diff --git a/config/alfresco/bootstrap-context.xml b/config/alfresco/bootstrap-context.xml index 4b004076b5..401b8a5195 100644 --- a/config/alfresco/bootstrap-context.xml +++ b/config/alfresco/bootstrap-context.xml @@ -57,6 +57,9 @@ + + + @@ -100,8 +103,13 @@ classpath:alfresco/dbscripts/create/${db.script.dialect}/AlfrescoPostCreate-JBPM-FK-indexes.sql - - classpath:alfresco/dbscripts/create/${db.script.dialect}/Schema-Reference.xml + + + classpath:alfresco/dbscripts/create/${db.script.dialect}/Schema-Reference-ALF.xml + classpath:alfresco/dbscripts/create/${db.script.dialect}/Schema-Reference-AVM.xml + classpath:alfresco/dbscripts/create/${db.script.dialect}/Schema-Reference-JBPM.xml + classpath:alfresco/dbscripts/create/${db.script.dialect}/Schema-Reference-ACT.xml + @@ -133,7 +141,6 @@ - @@ -147,6 +154,8 @@ + + @@ -175,11 +184,6 @@ - - - - - @@ -228,6 +232,11 @@ + + + + + @@ -247,18 +256,6 @@ - - - - - - - sitestore - - - alfresco - - diff --git a/config/alfresco/cache-context.xml b/config/alfresco/cache-context.xml index fede969a64..227ed1a573 100644 --- a/config/alfresco/cache-context.xml +++ b/config/alfresco/cache-context.xml @@ -77,7 +77,7 @@ org.alfresco.cache.contentDataTransactionalCache - + diff --git a/config/alfresco/content-services-context.xml b/config/alfresco/content-services-context.xml index f0a7701b2f..e55048b7fd 100644 --- a/config/alfresco/content-services-context.xml +++ b/config/alfresco/content-services-context.xml @@ -338,6 +338,27 @@ + + + + + application/vnd.openxmlformats-officedocument.spreadsheetml.sheet + image/jpeg + + + application/vnd.openxmlformats-officedocument.wordprocessingml.document + image/jpeg + + + application/vnd.openxmlformats-officedocument.presentationml.presentation + image/jpeg + + + + + @@ -458,6 +479,37 @@ + + + + + application/vnd.openxmlformats-officedocument.spreadsheetml.sheet + image/png + + + application/vnd.openxmlformats-officedocument.wordprocessingml.document + image/png + + + application/vnd.openxmlformats-officedocument.presentationml.presentation + image/png + + + + + + + + + + + + image/jpeg + + + alfresco.messages.wdr-messages alfresco.messages.subscription-service alfresco.messages.replication + alfresco.messages.categories diff --git a/config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/Schema-Reference-ACT.xml b/config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/Schema-Reference-ACT.xml new file mode 100644 index 0000000000..3c662ea50c --- /dev/null +++ b/config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/Schema-Reference-ACT.xml @@ -0,0 +1,1312 @@ + + + + + + .* + + + + + + + + + varchar(64) + false + false + + + int + true + false + + + varchar(255) + true + false + + + varchar(64) + true + false + + + longblob + true + false + + + + + ID_ + + + + + DEPLOYMENT_ID_ + ACT_RE_DEPLOYMENT + ID_ + + + + + + DEPLOYMENT_ID_ + + + +
+ + + + varchar(64) + false + false + + + varchar(300) + true + false + + + int + true + false + + + + + NAME_ + + + + +
+ + + + varchar(64) + false + false + + + int + true + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + varchar(4000) + true + false + + + varchar(255) + true + false + + + varchar(64) + true + false + + + varchar(64) + true + false + + + varchar(4000) + true + false + + + varchar(64) + true + false + + + + + ID_ + + + + +
+ + + + varchar(64) + false + false + + + varchar(255) + true + false + + + datetime + false + false + + + varchar(255) + true + false + + + varchar(64) + true + false + + + varchar(64) + true + false + + + varchar(255) + true + false + + + varchar(4000) + true + false + + + longblob + true + false + + + + + ID_ + + + + +
+ + + + varchar(64) + false + false + + + varchar(64) + true + false + + + varchar(255) + true + false + + + varchar(64) + true + false + + + varchar(64) + true + false + + + varchar(255) + true + false + + + varchar(64) + true + false + + + varchar(4000) + true + false + + + varchar(64) + true + false + + + varchar(64) + true + false + + + datetime + false + false + + + datetime + true + false + + + bigint + true + false + + + varchar(4000) + true + false + + + int + true + false + + + datetime + true + false + + + + + ID_ + + + + +
+ + + + varchar(64) + false + false + + + int + true + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + + + ID_ + + + + +
+ + + + varchar(64) + false + false + + + int + true + false + + + varchar(64) + true + false + + + varchar(64) + true + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + longblob + true + false + + + varchar(255) + true + false + + + + + ID_ + + + + +
+ + + + varchar(64) + false + false + + + varchar(64) + false + false + + + + + GROUP_ID_ + USER_ID_ + + + + + USER_ID_ + ACT_ID_USER + ID_ + + + GROUP_ID_ + ACT_ID_GROUP + ID_ + + + + + + GROUP_ID_ + + + +
+ + + + varchar(64) + false + false + + + int + true + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + varchar(64) + true + false + + + + + ID_ + + + + +
+ + + + varchar(64) + false + false + + + varchar(255) + true + false + + + timestamp + false + false + + + + + ID_ + + + + +
+ + + + varchar(64) + false + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + int + true + false + + + varchar(64) + true + false + + + varchar(4000) + true + false + + + varchar(4000) + true + false + + + tinyint + true + false + + + + + ID_ + + + + +
+ + + + varchar(64) + false + false + + + int + true + false + + + varchar(64) + true + false + + + varchar(255) + true + false + + + varchar(64) + true + false + + + varchar(64) + true + false + + + varchar(64) + true + false + + + varchar(255) + true + false + + + tinyint + true + false + + + tinyint + true + false + + + tinyint + true + false + + + + + ID_ + + + + + SUPER_EXEC_ + ACT_RU_EXECUTION + ID_ + + + PARENT_ID_ + ACT_RU_EXECUTION + ID_ + + + PROC_INST_ID_ + ACT_RU_EXECUTION + ID_ + + + + + + PROC_DEF_ID_ + BUSINESS_KEY_ + + + + + BUSINESS_KEY_ + + + + + PROC_INST_ID_ + + + + + PARENT_ID_ + + + + + SUPER_EXEC_ + + + +
+ + + + varchar(64) + false + false + + + int + true + false + + + varchar(64) + true + false + + + varchar(255) + true + false + + + varchar(64) + true + false + + + varchar(64) + true + false + + + + + ID_ + + + + + TASK_ID_ + ACT_RU_TASK + ID_ + + + + + + USER_ID_ + + + + + GROUP_ID_ + + + + + TASK_ID_ + + + +
+ + + + varchar(64) + false + false + + + int + true + false + + + varchar(255) + false + false + + + timestamp + false + false + + + varchar(255) + true + false + + + bit + true + false + + + varchar(64) + true + false + + + varchar(64) + true + false + + + int + true + false + + + varchar(64) + true + false + + + varchar(4000) + true + false + + + timestamp + true + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + varchar(4000) + true + false + + + + + ID_ + + + + + EXCEPTION_STACK_ID_ + ACT_GE_BYTEARRAY + ID_ + + + + + + EXCEPTION_STACK_ID_ + + + +
+ + + + varchar(64) + false + false + + + int + true + false + + + varchar(64) + true + false + + + varchar(64) + true + false + + + varchar(64) + true + false + + + varchar(255) + true + false + + + varchar(64) + true + false + + + varchar(4000) + true + false + + + varchar(255) + true + false + + + varchar(64) + true + false + + + varchar(64) + true + false + + + varchar(64) + true + false + + + int + true + false + + + timestamp + false + false + + + datetime + true + false + + + + + ID_ + + + + + PROC_DEF_ID_ + ACT_RE_PROCDEF + ID_ + + + EXECUTION_ID_ + ACT_RU_EXECUTION + ID_ + + + PROC_INST_ID_ + ACT_RU_EXECUTION + ID_ + + + + + + CREATE_TIME_ + + + + + EXECUTION_ID_ + + + + + PROC_INST_ID_ + + + + + PROC_DEF_ID_ + + + +
+ + + + varchar(64) + false + false + + + int + true + false + + + varchar(255) + false + false + + + varchar(255) + false + false + + + varchar(64) + true + false + + + varchar(64) + true + false + + + varchar(64) + true + false + + + varchar(64) + true + false + + + double + true + false + + + bigint + true + false + + + varchar(4000) + true + false + + + varchar(4000) + true + false + + + + + ID_ + + + + + BYTEARRAY_ID_ + ACT_GE_BYTEARRAY + ID_ + + + EXECUTION_ID_ + ACT_RU_EXECUTION + ID_ + + + PROC_INST_ID_ + ACT_RU_EXECUTION + ID_ + + + + + + EXECUTION_ID_ + + + + + PROC_INST_ID_ + + + + + BYTEARRAY_ID_ + + + +
+ + + + varchar(64) + false + false + + + varchar(64) + false + false + + + varchar(64) + false + false + + + varchar(64) + false + false + + + varchar(255) + false + false + + + varchar(255) + true + false + + + varchar(255) + false + false + + + varchar(64) + true + false + + + datetime + false + false + + + datetime + true + false + + + bigint + true + false + + + + + ID_ + + + + + + + START_TIME_ + + + + + END_TIME_ + + + +
+ + + + varchar(64) + false + false + + + varchar(255) + false + false + + + varchar(64) + false + false + + + varchar(64) + false + false + + + varchar(64) + true + false + + + varchar(64) + true + false + + + varchar(255) + false + false + + + varchar(255) + true + false + + + int + true + false + + + datetime + false + false + + + varchar(64) + true + false + + + double + true + false + + + bigint + true + false + + + varchar(4000) + true + false + + + varchar(4000) + true + false + + + + + ID_ + + + + + + + PROC_INST_ID_ + + + + + ACT_INST_ID_ + + + + + TIME_ + + + + + NAME_ + + + +
+ + + + varchar(64) + false + false + + + varchar(64) + false + false + + + varchar(255) + true + false + + + varchar(64) + false + false + + + datetime + false + false + + + datetime + true + false + + + bigint + true + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + varchar(64) + true + false + + + + + ID_ + + + + + + + PROC_INST_ID_ + + + + + PROC_DEF_ID_ + BUSINESS_KEY_ + + + + + END_TIME_ + + + + + BUSINESS_KEY_ + + + +
+
+
diff --git a/config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/Schema-Reference.xml b/config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/Schema-Reference-ALF.xml similarity index 96% rename from config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/Schema-Reference.xml rename to config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/Schema-Reference-ALF.xml index 2ebba870e0..f86e436319 100644 --- a/config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/Schema-Reference.xml +++ b/config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/Schema-Reference-ALF.xml @@ -1,5 +1,13 @@ - + + + + + .* + + + + diff --git a/config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/Schema-Reference-AVM.xml b/config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/Schema-Reference-AVM.xml new file mode 100644 index 0000000000..893b0908ec --- /dev/null +++ b/config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/Schema-Reference-AVM.xml @@ -0,0 +1,737 @@ + + + + + + .* + + + + + +
+ + + bigint + false + false + + + bigint + false + false + + + + + node_id + qname_id + + + + + qname_id + alf_qname + id + + + node_id + avm_nodes + id + + + + + + node_id + + + + + qname_id + + + +
+ + + + bigint + false + false + + + varchar(160) + false + false + + + bigint + false + false + + + + + name + parent_id + + + + + parent_id + avm_nodes + id + + + child_id + avm_nodes + id + + + + + + child_id + + + + + parent_id + + + +
+ + + + bigint + false + false + + + bigint + false + false + + + + + ancestor + descendent + + + + + ancestor + avm_nodes + id + + + descendent + avm_nodes + id + + + + + + descendent + + + + + ancestor + + + + + descendent + ancestor + + + +
+ + + + bigint + false + false + + + bigint + false + false + + + + + mfrom + mto + + + + + mto + avm_nodes + id + + + mfrom + avm_nodes + id + + + + + + mfrom + + + + + mto + + + +
+ + + + bigint + false + false + + + int + false + false + + + int + false + false + + + bit + false + false + + + bit + true + false + + + bigint + true + false + + + float + true + false + + + double + true + false + + + text + true + false + + + blob + true + false + + + bigint + false + false + + + + + node_id + qname_id + + + + + qname_id + alf_qname + id + + + node_id + avm_nodes + id + + + + + + node_id + + + + + qname_id + + + +
+ + + + bigint + false + true + + + varchar(20) + false + false + + + bigint + false + false + + + int + false + false + + + varchar(36) + true + false + + + varchar(255) + false + false + + + varchar(255) + false + false + + + varchar(255) + false + false + + + bigint + false + false + + + bigint + false + false + + + bigint + false + false + + + bit + true + false + + + bigint + true + false + + + bigint + true + false + + + int + true + false + + + bigint + true + false + + + text + true + false + + + int + true + false + + + bit + true + false + + + bit + true + false + + + varchar(128) + true + false + + + varchar(100) + true + false + + + varchar(16) + true + false + + + bigint + true + false + + + + + id + + + + + store_new_id + avm_stores + id + + + acl_id + alf_access_control_list + id + + + + + + primary_indirection + + + + + acl_id + + + + + store_new_id + + + +
+ + + + bigint + false + true + + + bigint + true + false + + + bigint + false + false + + + int + false + false + + + int + false + false + + + bit + false + false + + + bit + true + false + + + bigint + true + false + + + float + true + false + + + double + true + false + + + text + true + false + + + blob + true + false + + + + + id + + + + + qname_id + alf_qname + id + + + avm_store_id + avm_stores + id + + + + + + avm_store_id + + + + + qname_id + + + +
+ + + + bigint + false + true + + + bigint + false + false + + + varchar(255) + true + false + + + int + false + false + + + bigint + true + false + + + bigint + true + false + + + + + id + + + + + acl_id + alf_access_control_list + id + + + current_root_id + avm_nodes + id + + + + + + name + + + + + current_root_id + + + + + acl_id + + + +
+ + + + bigint + false + false + + + varchar(32) + false + false + + + text + true + false + + + + + md5sum + version_root_id + + + + + version_root_id + avm_version_roots + id + + + + + + version_root_id + + + +
+ + + + bigint + false + true + + + int + false + false + + + bigint + false + false + + + bigint + false + false + + + varchar(255) + false + false + + + bigint + false + false + + + varchar(255) + true + false + + + text + true + false + + + + + id + + + + + root_id + avm_nodes + id + + + avm_store_id + avm_stores + id + + + + + + version_id + avm_store_id + + + + + version_id + + + + + avm_store_id + + + + + root_id + + + + + avm_store_id + version_id + + + +
+
+
diff --git a/config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/Schema-Reference-JBPM.xml b/config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/Schema-Reference-JBPM.xml new file mode 100644 index 0000000000..9e681187a0 --- /dev/null +++ b/config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/Schema-Reference-JBPM.xml @@ -0,0 +1,2663 @@ + + + + + + .* + + + + + + + + + bigint + false + true + + + char(1) + false + false + + + varchar(255) + true + false + + + bit + true + false + + + varchar(255) + true + false + + + bit + true + false + + + bigint + true + false + + + bigint + true + false + + + bigint + true + false + + + bigint + true + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + bigint + true + false + + + text + true + false + + + int + true + false + + + bigint + true + false + + + int + true + false + + + + + ID_ + + + + + EXCEPTIONHANDLER_ + JBPM_EXCEPTIONHANDLER + ID_ + + + ACTIONDELEGATION_ + JBPM_DELEGATION + ID_ + + + EVENT_ + JBPM_EVENT + ID_ + + + PROCESSDEFINITION_ + JBPM_PROCESSDEFINITION + ID_ + + + REFERENCEDACTION_ + JBPM_ACTION + ID_ + + + TIMERACTION_ + JBPM_ACTION + ID_ + + + + + + REFERENCEDACTION_ + + + + + TIMERACTION_ + + + + + PROCESSDEFINITION_ + + + + + EVENT_ + + + + + ACTIONDELEGATION_ + + + + + EXCEPTIONHANDLER_ + + + +
+ + + + bigint + false + true + + + varchar(255) + true + false + + + bigint + true + false + + + + + ID_ + + + + + FILEDEFINITION_ + JBPM_MODULEDEFINITION + ID_ + + + + + + FILEDEFINITION_ + + + +
+ + + + bigint + false + false + + + blob + true + false + + + int + false + false + + + + + INDEX_ + PROCESSFILE_ + + + + + PROCESSFILE_ + JBPM_BYTEARRAY + ID_ + + + + + + PROCESSFILE_ + + + +
+ + + + bigint + false + true + + + int + false + false + + + varchar(255) + true + false + + + datetime + true + false + + + text + true + false + + + bigint + true + false + + + bigint + true + false + + + int + true + false + + + int + true + false + + + + + ID_ + + + + + TASKINSTANCE_ + JBPM_TASKINSTANCE + ID_ + + + TOKEN_ + JBPM_TOKEN + ID_ + + + + + + TOKEN_ + + + + + TASKINSTANCE_ + + + +
+ + + + bigint + false + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + int + false + false + + + + + DECISION_ + INDEX_ + + + + + DECISION_ + JBPM_NODE + ID_ + + + + + + DECISION_ + + + +
+ + + + bigint + false + true + + + text + true + false + + + text + true + false + + + varchar(255) + true + false + + + bigint + true + false + + + + + ID_ + + + + + PROCESSDEFINITION_ + JBPM_PROCESSDEFINITION + ID_ + + + + + + PROCESSDEFINITION_ + + + +
+ + + + bigint + false + true + + + varchar(255) + true + false + + + char(1) + true + false + + + bigint + true + false + + + bigint + true + false + + + bigint + true + false + + + bigint + true + false + + + bigint + true + false + + + + + ID_ + + + + + TASK_ + JBPM_TASK + ID_ + + + NODE_ + JBPM_NODE + ID_ + + + PROCESSDEFINITION_ + JBPM_PROCESSDEFINITION + ID_ + + + TRANSITION_ + JBPM_TRANSITION + ID_ + + + + + + PROCESSDEFINITION_ + + + + + TRANSITION_ + + + + + NODE_ + + + + + TASK_ + + + +
+ + + + bigint + false + true + + + text + true + false + + + char(1) + true + false + + + bigint + true + false + + + bigint + true + false + + + int + true + false + + + bigint + true + false + + + bigint + true + false + + + bigint + true + false + + + + + ID_ + + + + +
+ + + + bigint + false + true + + + char(1) + false + false + + + int + false + false + + + datetime + true + false + + + bigint + true + false + + + bigint + true + false + + + bigint + true + false + + + bit + true + false + + + bit + true + false + + + varchar(255) + true + false + + + datetime + true + false + + + text + true + false + + + int + true + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + bigint + true + false + + + varchar(255) + true + false + + + bigint + true + false + + + bigint + true + false + + + + + ID_ + + + + + TASKINSTANCE_ + JBPM_TASKINSTANCE + ID_ + + + ACTION_ + JBPM_ACTION + ID_ + + + NODE_ + JBPM_NODE + ID_ + + + PROCESSINSTANCE_ + JBPM_PROCESSINSTANCE + ID_ + + + TOKEN_ + JBPM_TOKEN + ID_ + + + + + + PROCESSINSTANCE_ + + + + + ACTION_ + + + + + TOKEN_ + + + + + NODE_ + + + + + TASKINSTANCE_ + + + +
+ + + + bigint + false + true + + + char(1) + false + false + + + int + true + false + + + datetime + true + false + + + bigint + true + false + + + bigint + true + false + + + text + true + false + + + text + true + false + + + bigint + true + false + + + bigint + true + false + + + datetime + true + false + + + datetime + true + false + + + bigint + true + false + + + bigint + true + false + + + bigint + true + false + + + bigint + true + false + + + bigint + true + false + + + bigint + true + false + + + bigint + true + false + + + bigint + true + false + + + bigint + true + false + + + datetime + true + false + + + datetime + true + false + + + double + true + false + + + double + true + false + + + varchar(255) + true + false + + + bigint + true + false + + + varchar(255) + true + false + + + bigint + true + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + bigint + true + false + + + text + true + false + + + text + true + false + + + bigint + true + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + bigint + true + false + + + + + ID_ + + + + + PARENT_ + JBPM_LOG + ID_ + + + ACTION_ + JBPM_ACTION + ID_ + + + CHILD_ + JBPM_TOKEN + ID_ + + + DESTINATIONNODE_ + JBPM_NODE + ID_ + + + NEWBYTEARRAY_ + JBPM_BYTEARRAY + ID_ + + + NODE_ + JBPM_NODE + ID_ + + + OLDBYTEARRAY_ + JBPM_BYTEARRAY + ID_ + + + SOURCENODE_ + JBPM_NODE + ID_ + + + SWIMLANEINSTANCE_ + JBPM_SWIMLANEINSTANCE + ID_ + + + TASKINSTANCE_ + JBPM_TASKINSTANCE + ID_ + + + TOKEN_ + JBPM_TOKEN + ID_ + + + TRANSITION_ + JBPM_TRANSITION + ID_ + + + VARIABLEINSTANCE_ + JBPM_VARIABLEINSTANCE + ID_ + + + + + + SOURCENODE_ + + + + + DESTINATIONNODE_ + + + + + TOKEN_ + + + + + TRANSITION_ + + + + + TASKINSTANCE_ + + + + + CHILD_ + + + + + OLDBYTEARRAY_ + + + + + SWIMLANEINSTANCE_ + + + + + NEWBYTEARRAY_ + + + + + ACTION_ + + + + + VARIABLEINSTANCE_ + + + + + NODE_ + + + + + PARENT_ + + + +
+ + + + bigint + false + true + + + char(1) + false + false + + + varchar(255) + true + false + + + bigint + true + false + + + bigint + true + false + + + + + ID_ + + + + + STARTTASK_ + JBPM_TASK + ID_ + + + PROCESSDEFINITION_ + JBPM_PROCESSDEFINITION + ID_ + + + + + + PROCESSDEFINITION_ + + + + + STARTTASK_ + + + +
+ + + + bigint + false + true + + + char(1) + false + false + + + int + false + false + + + bigint + true + false + + + bigint + true + false + + + varchar(255) + true + false + + + + + ID_ + + + + + TASKMGMTDEFINITION_ + JBPM_MODULEDEFINITION + ID_ + + + PROCESSINSTANCE_ + JBPM_PROCESSINSTANCE + ID_ + + + + + + PROCESSINSTANCE_ + + + + + TASKMGMTDEFINITION_ + + + +
+ + + + bigint + false + true + + + char(1) + false + false + + + varchar(255) + true + false + + + text + true + false + + + bigint + true + false + + + bit + true + false + + + bit + true + false + + + bigint + true + false + + + bigint + true + false + + + varchar(255) + true + false + + + bigint + true + false + + + varchar(255) + true + false + + + bigint + true + false + + + bigint + true + false + + + int + true + false + + + bit + true + false + + + bit + true + false + + + int + true + false + + + + + ID_ + + + + + SUPERSTATE_ + JBPM_NODE + ID_ + + + DECISIONDELEGATION + JBPM_DELEGATION + ID_ + + + ACTION_ + JBPM_ACTION + ID_ + + + PROCESSDEFINITION_ + JBPM_PROCESSDEFINITION + ID_ + + + SCRIPT_ + JBPM_ACTION + ID_ + + + SUBPROCESSDEFINITION_ + JBPM_PROCESSDEFINITION + ID_ + + + + + + DECISIONDELEGATION + + + + + PROCESSDEFINITION_ + + + + + ACTION_ + + + + + SUBPROCESSDEFINITION_ + + + + + SCRIPT_ + + + + + SUPERSTATE_ + + + +
+ + + + bigint + false + true + + + int + false + false + + + varchar(255) + true + false + + + bigint + true + false + + + + + ID_ + + + + + SWIMLANEINSTANCE_ + JBPM_SWIMLANEINSTANCE + ID_ + + + + + + SWIMLANEINSTANCE_ + + + +
+ + + + bigint + false + true + + + char(1) + false + false + + + varchar(255) + true + false + + + text + true + false + + + int + true + false + + + bit + true + false + + + bigint + true + false + + + + + ID_ + + + + + STARTSTATE_ + JBPM_NODE + ID_ + + + + + + STARTSTATE_ + + + +
+ + + + bigint + false + true + + + int + false + false + + + varchar(255) + true + false + + + datetime + true + false + + + datetime + true + false + + + bit + true + false + + + bigint + true + false + + + bigint + true + false + + + bigint + true + false + + + + + ID_ + + + + + SUPERPROCESSTOKEN_ + JBPM_TOKEN + ID_ + + + PROCESSDEFINITION_ + JBPM_PROCESSDEFINITION + ID_ + + + ROOTTOKEN_ + JBPM_TOKEN + ID_ + + + + + + PROCESSDEFINITION_ + + + + + ROOTTOKEN_ + + + + + SUPERPROCESSTOKEN_ + + + +
+ + + + bigint + false + true + + + int + false + false + + + varchar(255) + true + false + + + char(1) + true + false + + + bigint + true + false + + + bigint + true + false + + + bigint + true + false + + + int + true + false + + + + + ID_ + + + + + ACTION_ + JBPM_ACTION + ID_ + + + PROCESSINSTANCE_ + JBPM_PROCESSINSTANCE + ID_ + + + + + + PROCESSINSTANCE_ + + + + + ACTION_ + + + +
+ + + + bigint + false + true + + + varchar(255) + true + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + bigint + true + false + + + bigint + true + false + + + + + ID_ + + + + + TASKMGMTDEFINITION_ + JBPM_MODULEDEFINITION + ID_ + + + ASSIGNMENTDELEGATION_ + JBPM_DELEGATION + ID_ + + + + + + ASSIGNMENTDELEGATION_ + + + + + TASKMGMTDEFINITION_ + + + +
+ + + + bigint + false + true + + + int + false + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + bigint + true + false + + + bigint + true + false + + + + + ID_ + + + + + SWIMLANE_ + JBPM_SWIMLANE + ID_ + + + TASKMGMTINSTANCE_ + JBPM_MODULEINSTANCE + ID_ + + + + + + TASKMGMTINSTANCE_ + + + + + SWIMLANE_ + + + +
+ + + + bigint + false + true + + + varchar(255) + true + false + + + text + true + false + + + bigint + true + false + + + bit + true + false + + + bit + true + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + int + true + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + bigint + true + false + + + bigint + true + false + + + bigint + true + false + + + bigint + true + false + + + bigint + true + false + + + bigint + true + false + + + + + ID_ + + + + + TASKCONTROLLER_ + JBPM_TASKCONTROLLER + ID_ + + + ASSIGNMENTDELEGATION_ + JBPM_DELEGATION + ID_ + + + PROCESSDEFINITION_ + JBPM_PROCESSDEFINITION + ID_ + + + STARTSTATE_ + JBPM_NODE + ID_ + + + SWIMLANE_ + JBPM_SWIMLANE + ID_ + + + TASKMGMTDEFINITION_ + JBPM_MODULEDEFINITION + ID_ + + + TASKNODE_ + JBPM_NODE + ID_ + + + + + + STARTSTATE_ + + + + + PROCESSDEFINITION_ + + + + + ASSIGNMENTDELEGATION_ + + + + + SWIMLANE_ + + + + + TASKNODE_ + + + + + TASKMGMTDEFINITION_ + + + + + TASKCONTROLLER_ + + + +
+ + + + bigint + false + false + + + bigint + false + false + + + + + POOLEDACTOR_ + TASKINSTANCE_ + + + + + POOLEDACTOR_ + JBPM_POOLEDACTOR + ID_ + + + TASKINSTANCE_ + JBPM_TASKINSTANCE + ID_ + + + + + + TASKINSTANCE_ + + + + + POOLEDACTOR_ + + + +
+ + + + bigint + false + true + + + bigint + true + false + + + + + ID_ + + + + + TASKCONTROLLERDELEGATION_ + JBPM_DELEGATION + ID_ + + + + + + TASKCONTROLLERDELEGATION_ + + + +
+ + + + bigint + false + true + + + char(1) + false + false + + + int + false + false + + + varchar(255) + true + false + + + text + true + false + + + varchar(255) + true + false + + + datetime + true + false + + + datetime + true + false + + + datetime + true + false + + + datetime + true + false + + + int + true + false + + + bit + true + false + + + bit + true + false + + + bit + true + false + + + bit + true + false + + + bit + true + false + + + bigint + true + false + + + bigint + true + false + + + bigint + true + false + + + bigint + true + false + + + bigint + true + false + + + varchar(50) + true + false + + + + + ID_ + + + + + TASK_ + JBPM_TASK + ID_ + + + SWIMLANINSTANCE_ + JBPM_SWIMLANEINSTANCE + ID_ + + + TASKMGMTINSTANCE_ + JBPM_MODULEINSTANCE + ID_ + + + TOKEN_ + JBPM_TOKEN + ID_ + + + PROCINST_ + JBPM_PROCESSINSTANCE + ID_ + + + + + + PROCINST_ + + + + + TASKMGMTINSTANCE_ + + + + + TOKEN_ + + + + + SWIMLANINSTANCE_ + + + + + TASK_ + + + +
+ + + + bigint + false + true + + + int + false + false + + + varchar(255) + true + false + + + datetime + true + false + + + datetime + true + false + + + datetime + true + false + + + int + true + false + + + bit + true + false + + + bit + true + false + + + bit + true + false + + + varchar(255) + true + false + + + bigint + true + false + + + bigint + true + false + + + bigint + true + false + + + bigint + true + false + + + + + ID_ + + + + + PARENT_ + JBPM_TOKEN + ID_ + + + NODE_ + JBPM_NODE + ID_ + + + PROCESSINSTANCE_ + JBPM_PROCESSINSTANCE + ID_ + + + SUBPROCESSINSTANCE_ + JBPM_PROCESSINSTANCE + ID_ + + + + + + SUBPROCESSINSTANCE_ + + + + + PROCESSINSTANCE_ + + + + + NODE_ + + + + + PARENT_ + + + +
+ + + + bigint + false + true + + + int + false + false + + + bigint + true + false + + + bigint + true + false + + + + + ID_ + + + + + CONTEXTINSTANCE_ + JBPM_MODULEINSTANCE + ID_ + + + TOKEN_ + JBPM_TOKEN + ID_ + + + + + + TOKEN_ + + + + + CONTEXTINSTANCE_ + + + +
+ + + + bigint + false + true + + + varchar(255) + true + false + + + text + true + false + + + bigint + true + false + + + bigint + true + false + + + bigint + true + false + + + varchar(255) + true + false + + + int + true + false + + + + + ID_ + + + + + TO_ + JBPM_NODE + ID_ + + + FROM_ + JBPM_NODE + ID_ + + + PROCESSDEFINITION_ + JBPM_PROCESSDEFINITION + ID_ + + + + + + FROM_ + + + + + PROCESSDEFINITION_ + + + + + TO_ + + + +
+ + + + bigint + false + true + + + varchar(255) + true + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + bigint + true + false + + + bigint + true + false + + + int + true + false + + + bigint + true + false + + + + + ID_ + + + + + TASKCONTROLLER_ + JBPM_TASKCONTROLLER + ID_ + + + PROCESSSTATE_ + JBPM_NODE + ID_ + + + SCRIPT_ + JBPM_ACTION + ID_ + + + + + + PROCESSSTATE_ + + + + + SCRIPT_ + + + + + TASKCONTROLLER_ + + + +
+ + + + bigint + false + true + + + char(1) + false + false + + + int + false + false + + + varchar(255) + true + false + + + char(1) + true + false + + + bigint + true + false + + + bigint + true + false + + + bigint + true + false + + + bigint + true + false + + + datetime + true + false + + + double + true + false + + + varchar(255) + true + false + + + bigint + true + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + bigint + true + false + + + + + ID_ + + + + + TASKINSTANCE_ + JBPM_TASKINSTANCE + ID_ + + + BYTEARRAYVALUE_ + JBPM_BYTEARRAY + ID_ + + + PROCESSINSTANCE_ + JBPM_PROCESSINSTANCE + ID_ + + + TOKEN_ + JBPM_TOKEN + ID_ + + + TOKENVARIABLEMAP_ + JBPM_TOKENVARIABLEMAP + ID_ + + + + + + PROCESSINSTANCE_ + + + + + TOKENVARIABLEMAP_ + + + + + TOKEN_ + + + + + BYTEARRAYVALUE_ + + + + + TASKINSTANCE_ + + + +
+
+
diff --git a/config/alfresco/dbscripts/create/org.hibernate.dialect.PostgreSQLDialect/Schema-Reference-ACT.xml b/config/alfresco/dbscripts/create/org.hibernate.dialect.PostgreSQLDialect/Schema-Reference-ACT.xml new file mode 100644 index 0000000000..c58389858a --- /dev/null +++ b/config/alfresco/dbscripts/create/org.hibernate.dialect.PostgreSQLDialect/Schema-Reference-ACT.xml @@ -0,0 +1,1317 @@ + + + + + + .* + + + + + + + + + varchar(64) + false + false + + + int4 + true + false + + + varchar(255) + true + false + + + varchar(64) + true + false + + + bytea + true + false + + + + + id_ + + + + + deployment_id_ + act_re_deployment + id_ + + + + + + deployment_id_ + + + +
+ + + + varchar(64) + false + false + + + varchar(300) + true + false + + + int4 + true + false + + + + + name_ + + + + +
+ + + + varchar(64) + false + false + + + varchar(64) + false + false + + + varchar(64) + false + false + + + varchar(64) + false + false + + + varchar(255) + false + false + + + varchar(255) + true + false + + + varchar(255) + false + false + + + varchar(64) + true + false + + + timestamp + false + false + + + timestamp + true + false + + + int8 + true + false + + + + + id_ + + + + + + + end_time_ + + + + + start_time_ + + + +
+ + + + varchar(64) + false + false + + + int4 + true + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + varchar(4000) + true + false + + + varchar(255) + true + false + + + varchar(64) + true + false + + + varchar(64) + true + false + + + varchar(4000) + true + false + + + varchar(64) + true + false + + + + + id_ + + + + +
+ + + + varchar(64) + false + false + + + varchar(255) + true + false + + + timestamp + false + false + + + varchar(255) + true + false + + + varchar(64) + true + false + + + varchar(64) + true + false + + + varchar(255) + true + false + + + varchar(4000) + true + false + + + bytea + true + false + + + + + id_ + + + + +
+ + + + varchar(64) + false + false + + + varchar(255) + false + false + + + varchar(64) + false + false + + + varchar(64) + false + false + + + varchar(64) + true + false + + + varchar(64) + true + false + + + varchar(255) + false + false + + + varchar(64) + true + false + + + int4 + true + false + + + timestamp + false + false + + + varchar(64) + true + false + + + float8 + true + false + + + int8 + true + false + + + varchar(4000) + true + false + + + varchar(4000) + true + false + + + + + id_ + + + + + + + act_inst_id_ + + + + + name_ + + + + + proc_inst_id_ + + + + + time_ + + + +
+ + + + varchar(64) + false + false + + + varchar(64) + false + false + + + varchar(255) + true + false + + + varchar(64) + false + false + + + timestamp + false + false + + + timestamp + true + false + + + int8 + true + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + varchar(64) + true + false + + + + + id_ + + + + + + + proc_def_id_ + business_key_ + + + + + proc_inst_id_ + + + + + business_key_ + + + + + end_time_ + + + +
+ + + + varchar(64) + false + false + + + varchar(64) + true + false + + + varchar(255) + true + false + + + varchar(64) + true + false + + + varchar(64) + true + false + + + varchar(255) + true + false + + + varchar(64) + true + false + + + varchar(4000) + true + false + + + varchar(64) + true + false + + + varchar(64) + true + false + + + timestamp + false + false + + + timestamp + true + false + + + int8 + true + false + + + varchar(4000) + true + false + + + int4 + true + false + + + timestamp + true + false + + + + + id_ + + + + +
+ + + + varchar(64) + false + false + + + int4 + true + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + + + id_ + + + + +
+ + + + varchar(64) + false + false + + + int4 + true + false + + + varchar(64) + true + false + + + varchar(64) + true + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + bytea + true + false + + + varchar(255) + true + false + + + + + id_ + + + + +
+ + + + varchar(64) + false + false + + + varchar(64) + false + false + + + + + user_id_ + group_id_ + + + + + group_id_ + act_id_group + id_ + + + user_id_ + act_id_user + id_ + + + + + + group_id_ + + + + + user_id_ + + + +
+ + + + varchar(64) + false + false + + + int4 + true + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + varchar(64) + true + false + + + + + id_ + + + + +
+ + + + varchar(64) + false + false + + + varchar(255) + true + false + + + timestamp + true + false + + + + + id_ + + + + +
+ + + + varchar(64) + false + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + int4 + true + false + + + varchar(64) + true + false + + + varchar(4000) + true + false + + + varchar(4000) + true + false + + + bool + true + false + + + + + id_ + + + + +
+ + + + varchar(64) + false + false + + + int4 + true + false + + + varchar(64) + true + false + + + varchar(255) + true + false + + + varchar(64) + true + false + + + varchar(64) + true + false + + + varchar(64) + true + false + + + varchar(255) + true + false + + + bool + true + false + + + bool + true + false + + + bool + true + false + + + + + id_ + + + + + super_exec_ + act_ru_execution + id_ + + + parent_id_ + act_ru_execution + id_ + + + proc_inst_id_ + act_ru_execution + id_ + + + + + + proc_def_id_ + business_key_ + + + + + parent_id_ + + + + + proc_inst_id_ + + + + + super_exec_ + + + + + business_key_ + + + +
+ + + + varchar(64) + false + false + + + int4 + true + false + + + varchar(64) + true + false + + + varchar(255) + true + false + + + varchar(64) + true + false + + + varchar(64) + true + false + + + + + id_ + + + + + task_id_ + act_ru_task + id_ + + + + + + group_id_ + + + + + user_id_ + + + + + task_id_ + + + +
+ + + + varchar(64) + false + false + + + int4 + true + false + + + varchar(255) + false + false + + + timestamp + true + false + + + varchar(255) + true + false + + + bool + true + false + + + varchar(64) + true + false + + + varchar(64) + true + false + + + int4 + true + false + + + varchar(64) + true + false + + + varchar(4000) + true + false + + + timestamp + true + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + varchar(4000) + true + false + + + + + id_ + + + + + exception_stack_id_ + act_ge_bytearray + id_ + + + + + + exception_stack_id_ + + + +
+ + + + varchar(64) + false + false + + + int4 + true + false + + + varchar(64) + true + false + + + varchar(64) + true + false + + + varchar(64) + true + false + + + varchar(255) + true + false + + + varchar(64) + true + false + + + varchar(4000) + true + false + + + varchar(255) + true + false + + + varchar(64) + true + false + + + varchar(64) + true + false + + + varchar(64) + true + false + + + int4 + true + false + + + timestamp + true + false + + + timestamp + true + false + + + + + id_ + + + + + proc_def_id_ + act_re_procdef + id_ + + + proc_inst_id_ + act_ru_execution + id_ + + + execution_id_ + act_ru_execution + id_ + + + + + + create_time_ + + + + + execution_id_ + + + + + proc_def_id_ + + + + + proc_inst_id_ + + + +
+ + + + varchar(64) + false + false + + + int4 + true + false + + + varchar(255) + false + false + + + varchar(255) + false + false + + + varchar(64) + true + false + + + varchar(64) + true + false + + + varchar(64) + true + false + + + varchar(64) + true + false + + + float8 + true + false + + + int8 + true + false + + + varchar(4000) + true + false + + + varchar(4000) + true + false + + + + + id_ + + + + + bytearray_id_ + act_ge_bytearray + id_ + + + proc_inst_id_ + act_ru_execution + id_ + + + execution_id_ + act_ru_execution + id_ + + + + + + bytearray_id_ + + + + + execution_id_ + + + + + proc_inst_id_ + + + +
+
+
diff --git a/config/alfresco/dbscripts/create/org.hibernate.dialect.PostgreSQLDialect/Schema-Reference.xml b/config/alfresco/dbscripts/create/org.hibernate.dialect.PostgreSQLDialect/Schema-Reference-ALF.xml similarity index 84% rename from config/alfresco/dbscripts/create/org.hibernate.dialect.PostgreSQLDialect/Schema-Reference.xml rename to config/alfresco/dbscripts/create/org.hibernate.dialect.PostgreSQLDialect/Schema-Reference-ALF.xml index 467bd9db1b..6106270b46 100644 --- a/config/alfresco/dbscripts/create/org.hibernate.dialect.PostgreSQLDialect/Schema-Reference.xml +++ b/config/alfresco/dbscripts/create/org.hibernate.dialect.PostgreSQLDialect/Schema-Reference-ALF.xml @@ -1,5 +1,13 @@ - + + + + + .* + + + + @@ -43,30 +51,37 @@ int8 false + false int8 false + false int8 false + false int8 false + false bool false + false int4 false + false int8 true + false @@ -122,50 +137,62 @@ int8 false + false int8 false + false varchar(36) false + false bool false + false int8 false + false bool false + false int8 true + false int4 false + false int8 true + false bool false + false bool false + false int8 true + false @@ -206,22 +233,27 @@ int8 false + false int8 false + false varchar(1024) true + false varchar(1024) true + false varchar(1024) true + false @@ -237,10 +269,12 @@ int8 false + false int8 true + false @@ -262,22 +296,27 @@ int8 false + false int8 false + false int8 false + false int8 false + false int4 false + false @@ -322,46 +361,57 @@ int8 false + false int8 true + false timestamp false + false varchar(1024) true + false varchar(255) true + false varchar(255) false + false varchar(10) true + false varchar(255) true + false varchar(36) true + false varchar(255) false + false timestamp false + false @@ -403,22 +453,27 @@ int8 false + false varchar(255) false + false varchar(255) true + false varchar(36) true + false timestamp false + false @@ -440,42 +495,52 @@ int8 false + false timestamp false + false varchar(10) false + false varchar(1024) false + false varchar(255) false + false int4 false + false varchar(255) true + false varchar(36) true + false varchar(255) false + false timestamp false + false @@ -502,46 +567,57 @@ varchar(64) false + false varchar(1024) true + false int4 true + false int4 true + false int4 true + false int4 true + false timestamp true + false varchar(64) true + false bool true + false bool true + false varchar(1024) true + false @@ -557,22 +633,27 @@ int8 false + false int4 false + false int8 false + false int8 false + false int8 false + false @@ -620,22 +701,27 @@ int8 false + false int8 false + false int8 false + false int8 true + false int8 true + false @@ -688,14 +774,17 @@ int8 false + false int8 false + false int8 false + false @@ -728,18 +817,22 @@ int8 false + false int8 false + false varchar(100) true + false int8 true + false @@ -767,18 +860,22 @@ int8 false + false int8 false + false int8 false + false int8 false + false @@ -822,50 +919,62 @@ int8 false + false int8 false + false int8 false + false int8 false + false int8 false + false varchar(50) false + false int8 false + false int8 false + false varchar(255) false + false int8 false + false bool true + false int4 true + false @@ -945,26 +1054,32 @@ int8 false + false int8 false + false int8 true + false int8 true + false int8 true + false int8 true + false @@ -1022,26 +1137,32 @@ int8 false + false varchar(255) false + false varchar(12) false + false int8 false + false int8 false + false int8 true + false @@ -1069,14 +1190,17 @@ int8 false + false int8 false + false varchar(100) false + false @@ -1098,14 +1222,17 @@ int8 false + false int8 false + false varchar(20) false + false @@ -1127,30 +1254,37 @@ int8 false + false int8 false + false int8 false + false int8 false + false varchar(36) false + false int8 false + false int8 false + false @@ -1189,18 +1323,22 @@ int8 false + false int8 false + false int8 false + false varchar(255) false + false @@ -1229,14 +1367,17 @@ int8 false + false int8 false + false varchar(100) false + false @@ -1258,14 +1399,17 @@ int8 false + false int8 false + false varchar(100) false + false @@ -1287,58 +1431,72 @@ int8 false + false int8 false + false int8 false + false varchar(36) false + false int8 false + false bool false + false int8 false + false int8 false + false int8 true + false varchar(255) true + false varchar(30) true + false varchar(255) true + false varchar(30) true + false varchar(30) true + false @@ -1423,10 +1581,12 @@ int8 false + false int8 false + false @@ -1465,26 +1625,32 @@ int8 false + false int8 false + false int8 false + false int8 false + false int8 false + false int8 false + false @@ -1542,50 +1708,62 @@ int8 false + false int4 false + false int4 false + false bool true + false int8 true + false float4 true + false float8 true + false varchar(1024) true + false bytea true + false int8 false + false int4 false + false int8 false + false @@ -1636,18 +1814,22 @@ int8 false + false int8 false + false int8 false + false varchar(100) false + false @@ -1681,18 +1863,22 @@ int8 false + false varchar(255) false + false varchar(32) false + false int8 false + false @@ -1720,42 +1906,52 @@ int8 false + false int4 false + false int2 false + false int2 false + false int2 false + false int2 false + false int2 false + false int4 false + false int2 false + false int2 false + false @@ -1779,10 +1975,12 @@ int8 false + false float8 false + false @@ -1804,22 +2002,27 @@ int8 false + false int8 false + false int8 false + false int8 false + false int8 false + false @@ -1871,10 +2074,12 @@ int8 false + false int4 false + false @@ -1890,10 +2095,12 @@ int8 false + false bytea false + false @@ -1909,18 +2116,22 @@ int8 false + false varchar(1024) false + false varchar(16) false + false int8 false + false @@ -1948,26 +2159,32 @@ int8 false + false int4 false + false int8 false + false int8 false + false int8 false + false int8 true + false @@ -2027,18 +2244,22 @@ int8 false + false int8 false + false int2 false + false int8 false + false @@ -2067,18 +2288,22 @@ int8 false + false int8 false + false int8 false + false varchar(200) false + false @@ -2112,14 +2337,17 @@ int8 false + false int8 false + false varchar(39) false + false @@ -2141,22 +2369,27 @@ int8 false + false int8 false + false varchar(50) false + false varchar(100) false + false int8 true + false @@ -2190,10 +2423,12 @@ int8 false + false int8 false + false @@ -2227,22 +2462,27 @@ int8 false + false int8 false + false int8 true + false varchar(56) false + false int8 true + false @@ -2276,18 +2516,22 @@ int8 false + false int8 false + false int8 false + false int8 false + false diff --git a/config/alfresco/dbscripts/create/org.hibernate.dialect.PostgreSQLDialect/Schema-Reference-AVM.xml b/config/alfresco/dbscripts/create/org.hibernate.dialect.PostgreSQLDialect/Schema-Reference-AVM.xml new file mode 100644 index 0000000000..4f879ff503 --- /dev/null +++ b/config/alfresco/dbscripts/create/org.hibernate.dialect.PostgreSQLDialect/Schema-Reference-AVM.xml @@ -0,0 +1,741 @@ + + + + + + .* + + + + + + + + + + + + + int8 + false + false + + + int8 + false + false + + + + + node_id + qname_id + + + + + qname_id + alf_qname + id + + + node_id + avm_nodes + id + + + + + + node_id + + + + + qname_id + + + +
+ + + + int8 + false + false + + + varchar(160) + false + false + + + int8 + false + false + + + + + parent_id + name + + + + + parent_id + avm_nodes + id + + + child_id + avm_nodes + id + + + + + + child_id + + + + + parent_id + + + +
+ + + + int8 + false + false + + + int8 + false + false + + + + + ancestor + descendent + + + + + ancestor + avm_nodes + id + + + descendent + avm_nodes + id + + + + + + ancestor + + + + + descendent + + + + + descendent + ancestor + + + +
+ + + + int8 + false + false + + + int8 + false + false + + + + + mfrom + mto + + + + + mto + avm_nodes + id + + + mfrom + avm_nodes + id + + + + + + mfrom + + + + + mto + + + +
+ + + + int8 + false + false + + + int4 + false + false + + + int4 + false + false + + + bool + false + false + + + bool + true + false + + + int8 + true + false + + + float4 + true + false + + + float8 + true + false + + + varchar(1024) + true + false + + + bytea + true + false + + + int8 + false + false + + + + + node_id + qname_id + + + + + qname_id + alf_qname + id + + + node_id + avm_nodes + id + + + + + + node_id + + + + + qname_id + + + +
+ + + + int8 + false + false + + + varchar(20) + false + false + + + int8 + false + false + + + int4 + false + false + + + varchar(36) + true + false + + + varchar(255) + false + false + + + varchar(255) + false + false + + + varchar(255) + false + false + + + int8 + false + false + + + int8 + false + false + + + int8 + false + false + + + bool + true + false + + + int8 + true + false + + + int8 + true + false + + + int4 + true + false + + + int8 + true + false + + + varchar(1024) + true + false + + + int4 + true + false + + + bool + true + false + + + bool + true + false + + + varchar(128) + true + false + + + varchar(100) + true + false + + + varchar(16) + true + false + + + int8 + true + false + + + + + id + + + + + acl_id + alf_access_control_list + id + + + store_new_id + avm_stores + id + + + + + + acl_id + + + + + store_new_id + + + + + primary_indirection + + + +
+ + + + int8 + false + false + + + int8 + true + false + + + int8 + false + false + + + int4 + false + false + + + int4 + false + false + + + bool + false + false + + + bool + true + false + + + int8 + true + false + + + float4 + true + false + + + float8 + true + false + + + varchar(1024) + true + false + + + bytea + true + false + + + + + id + + + + + qname_id + alf_qname + id + + + avm_store_id + avm_stores + id + + + + + + qname_id + + + + + avm_store_id + + + +
+ + + + int8 + false + false + + + int8 + false + false + + + varchar(255) + true + false + + + int4 + false + false + + + int8 + true + false + + + int8 + true + false + + + + + id + + + + + acl_id + alf_access_control_list + id + + + current_root_id + avm_nodes + id + + + + + + name + + + + + acl_id + + + + + current_root_id + + + +
+ + + + int8 + false + false + + + varchar(32) + false + false + + + varchar(1024) + true + false + + + + + version_root_id + md5sum + + + + + version_root_id + avm_version_roots + id + + + + + + version_root_id + + + +
+ + + + int8 + false + false + + + int4 + false + false + + + int8 + false + false + + + int8 + false + false + + + varchar(255) + false + false + + + int8 + false + false + + + varchar(255) + true + false + + + varchar(1024) + true + false + + + + + id + + + + + root_id + avm_nodes + id + + + avm_store_id + avm_stores + id + + + + + + version_id + avm_store_id + + + + + root_id + + + + + avm_store_id + + + + + avm_store_id + version_id + + + + + version_id + + + +
+
+
diff --git a/config/alfresco/dbscripts/create/org.hibernate.dialect.PostgreSQLDialect/Schema-Reference-JBPM.xml b/config/alfresco/dbscripts/create/org.hibernate.dialect.PostgreSQLDialect/Schema-Reference-JBPM.xml new file mode 100644 index 0000000000..2a63a18da8 --- /dev/null +++ b/config/alfresco/dbscripts/create/org.hibernate.dialect.PostgreSQLDialect/Schema-Reference-JBPM.xml @@ -0,0 +1,2663 @@ + + + + + + .* + + + + + + + + + int8 + false + false + + + bpchar + false + false + + + varchar(255) + true + false + + + bool + true + false + + + varchar(255) + true + false + + + bool + true + false + + + int8 + true + false + + + int8 + true + false + + + int8 + true + false + + + int8 + true + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + int8 + true + false + + + text + true + false + + + int4 + true + false + + + int8 + true + false + + + int4 + true + false + + + + + id_ + + + + + timeraction_ + jbpm_action + id_ + + + referencedaction_ + jbpm_action + id_ + + + actiondelegation_ + jbpm_delegation + id_ + + + event_ + jbpm_event + id_ + + + exceptionhandler_ + jbpm_exceptionhandler + id_ + + + processdefinition_ + jbpm_processdefinition + id_ + + + + + + actiondelegation_ + + + + + event_ + + + + + exceptionhandler_ + + + + + processdefinition_ + + + + + referencedaction_ + + + + + timeraction_ + + + +
+ + + + int8 + false + false + + + varchar(255) + true + false + + + int8 + true + false + + + + + id_ + + + + + filedefinition_ + jbpm_moduledefinition + id_ + + + + + + filedefinition_ + + + +
+ + + + int8 + false + false + + + bytea + true + false + + + int4 + false + false + + + + + processfile_ + index_ + + + + + processfile_ + jbpm_bytearray + id_ + + + + + + processfile_ + + + +
+ + + + int8 + false + false + + + int4 + false + false + + + varchar(255) + true + false + + + timestamp + true + false + + + text + true + false + + + int8 + true + false + + + int8 + true + false + + + int4 + true + false + + + int4 + true + false + + + + + id_ + + + + + taskinstance_ + jbpm_taskinstance + id_ + + + token_ + jbpm_token + id_ + + + + + + token_ + + + + + taskinstance_ + + + +
+ + + + int8 + false + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + int4 + false + false + + + + + decision_ + index_ + + + + + decision_ + jbpm_node + id_ + + + + + + decision_ + + + +
+ + + + int8 + false + false + + + text + true + false + + + text + true + false + + + varchar(255) + true + false + + + int8 + true + false + + + + + id_ + + + + + processdefinition_ + jbpm_processdefinition + id_ + + + + + + processdefinition_ + + + +
+ + + + int8 + false + false + + + varchar(255) + true + false + + + bpchar + true + false + + + int8 + true + false + + + int8 + true + false + + + int8 + true + false + + + int8 + true + false + + + int8 + true + false + + + + + id_ + + + + + node_ + jbpm_node + id_ + + + processdefinition_ + jbpm_processdefinition + id_ + + + task_ + jbpm_task + id_ + + + transition_ + jbpm_transition + id_ + + + + + + node_ + + + + + processdefinition_ + + + + + task_ + + + + + transition_ + + + +
+ + + + int8 + false + false + + + text + true + false + + + bpchar + true + false + + + int8 + true + false + + + int8 + true + false + + + int4 + true + false + + + int8 + true + false + + + int8 + true + false + + + int8 + true + false + + + + + id_ + + + + +
+ + + + int8 + false + false + + + bpchar + false + false + + + int4 + false + false + + + timestamp + true + false + + + int8 + true + false + + + int8 + true + false + + + int8 + true + false + + + bool + true + false + + + bool + true + false + + + varchar(255) + true + false + + + timestamp + true + false + + + text + true + false + + + int4 + true + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + int8 + true + false + + + varchar(255) + true + false + + + int8 + true + false + + + int8 + true + false + + + + + id_ + + + + + action_ + jbpm_action + id_ + + + node_ + jbpm_node + id_ + + + processinstance_ + jbpm_processinstance + id_ + + + taskinstance_ + jbpm_taskinstance + id_ + + + token_ + jbpm_token + id_ + + + + + + action_ + + + + + node_ + + + + + processinstance_ + + + + + token_ + + + + + taskinstance_ + + + +
+ + + + int8 + false + false + + + bpchar + false + false + + + int4 + true + false + + + timestamp + true + false + + + int8 + true + false + + + int8 + true + false + + + text + true + false + + + text + true + false + + + int8 + true + false + + + int8 + true + false + + + timestamp + true + false + + + timestamp + true + false + + + int8 + true + false + + + int8 + true + false + + + int8 + true + false + + + int8 + true + false + + + int8 + true + false + + + int8 + true + false + + + int8 + true + false + + + int8 + true + false + + + int8 + true + false + + + timestamp + true + false + + + timestamp + true + false + + + float8 + true + false + + + float8 + true + false + + + varchar(255) + true + false + + + int8 + true + false + + + varchar(255) + true + false + + + int8 + true + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + int8 + true + false + + + text + true + false + + + text + true + false + + + int8 + true + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + int8 + true + false + + + + + id_ + + + + + action_ + jbpm_action + id_ + + + oldbytearray_ + jbpm_bytearray + id_ + + + newbytearray_ + jbpm_bytearray + id_ + + + parent_ + jbpm_log + id_ + + + destinationnode_ + jbpm_node + id_ + + + node_ + jbpm_node + id_ + + + sourcenode_ + jbpm_node + id_ + + + swimlaneinstance_ + jbpm_swimlaneinstance + id_ + + + taskinstance_ + jbpm_taskinstance + id_ + + + token_ + jbpm_token + id_ + + + child_ + jbpm_token + id_ + + + transition_ + jbpm_transition + id_ + + + variableinstance_ + jbpm_variableinstance + id_ + + + + + + action_ + + + + + child_ + + + + + destinationnode_ + + + + + newbytearray_ + + + + + node_ + + + + + oldbytearray_ + + + + + parent_ + + + + + sourcenode_ + + + + + swimlaneinstance_ + + + + + taskinstance_ + + + + + token_ + + + + + transition_ + + + + + variableinstance_ + + + +
+ + + + int8 + false + false + + + bpchar + false + false + + + varchar(255) + true + false + + + int8 + true + false + + + int8 + true + false + + + + + id_ + + + + + processdefinition_ + jbpm_processdefinition + id_ + + + starttask_ + jbpm_task + id_ + + + + + + processdefinition_ + + + + + starttask_ + + + +
+ + + + int8 + false + false + + + bpchar + false + false + + + int4 + false + false + + + int8 + true + false + + + int8 + true + false + + + varchar(255) + true + false + + + + + id_ + + + + + taskmgmtdefinition_ + jbpm_moduledefinition + id_ + + + processinstance_ + jbpm_processinstance + id_ + + + + + + processinstance_ + + + + + taskmgmtdefinition_ + + + +
+ + + + int8 + false + false + + + bpchar + false + false + + + varchar(255) + true + false + + + text + true + false + + + int8 + true + false + + + bool + true + false + + + bool + true + false + + + int8 + true + false + + + int8 + true + false + + + varchar(255) + true + false + + + int8 + true + false + + + varchar(255) + true + false + + + int8 + true + false + + + int8 + true + false + + + int4 + true + false + + + bool + true + false + + + bool + true + false + + + int4 + true + false + + + + + id_ + + + + + script_ + jbpm_action + id_ + + + action_ + jbpm_action + id_ + + + decisiondelegation + jbpm_delegation + id_ + + + superstate_ + jbpm_node + id_ + + + subprocessdefinition_ + jbpm_processdefinition + id_ + + + processdefinition_ + jbpm_processdefinition + id_ + + + + + + decisiondelegation + + + + + action_ + + + + + processdefinition_ + + + + + script_ + + + + + superstate_ + + + + + subprocessdefinition_ + + + +
+ + + + int8 + false + false + + + int4 + false + false + + + varchar(255) + true + false + + + int8 + true + false + + + + + id_ + + + + + swimlaneinstance_ + jbpm_swimlaneinstance + id_ + + + + + + swimlaneinstance_ + + + +
+ + + + int8 + false + false + + + bpchar + false + false + + + varchar(255) + true + false + + + text + true + false + + + int4 + true + false + + + bool + true + false + + + int8 + true + false + + + + + id_ + + + + + startstate_ + jbpm_node + id_ + + + + + + startstate_ + + + +
+ + + + int8 + false + false + + + int4 + false + false + + + varchar(255) + true + false + + + timestamp + true + false + + + timestamp + true + false + + + bool + true + false + + + int8 + true + false + + + int8 + true + false + + + int8 + true + false + + + + + id_ + + + + + processdefinition_ + jbpm_processdefinition + id_ + + + superprocesstoken_ + jbpm_token + id_ + + + roottoken_ + jbpm_token + id_ + + + + + + processdefinition_ + + + + + roottoken_ + + + + + superprocesstoken_ + + + +
+ + + + int8 + false + false + + + int4 + false + false + + + varchar(255) + true + false + + + bpchar + true + false + + + int8 + true + false + + + int8 + true + false + + + int8 + true + false + + + int4 + true + false + + + + + id_ + + + + + action_ + jbpm_action + id_ + + + processinstance_ + jbpm_processinstance + id_ + + + + + + action_ + + + + + processinstance_ + + + +
+ + + + int8 + false + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + int8 + true + false + + + int8 + true + false + + + + + id_ + + + + + assignmentdelegation_ + jbpm_delegation + id_ + + + taskmgmtdefinition_ + jbpm_moduledefinition + id_ + + + + + + assignmentdelegation_ + + + + + taskmgmtdefinition_ + + + +
+ + + + int8 + false + false + + + int4 + false + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + int8 + true + false + + + int8 + true + false + + + + + id_ + + + + + taskmgmtinstance_ + jbpm_moduleinstance + id_ + + + swimlane_ + jbpm_swimlane + id_ + + + + + + swimlane_ + + + + + taskmgmtinstance_ + + + +
+ + + + int8 + false + false + + + varchar(255) + true + false + + + text + true + false + + + int8 + true + false + + + bool + true + false + + + bool + true + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + int4 + true + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + int8 + true + false + + + int8 + true + false + + + int8 + true + false + + + int8 + true + false + + + int8 + true + false + + + int8 + true + false + + + + + id_ + + + + + assignmentdelegation_ + jbpm_delegation + id_ + + + taskmgmtdefinition_ + jbpm_moduledefinition + id_ + + + tasknode_ + jbpm_node + id_ + + + startstate_ + jbpm_node + id_ + + + processdefinition_ + jbpm_processdefinition + id_ + + + swimlane_ + jbpm_swimlane + id_ + + + taskcontroller_ + jbpm_taskcontroller + id_ + + + + + + assignmentdelegation_ + + + + + processdefinition_ + + + + + startstate_ + + + + + swimlane_ + + + + + taskmgmtdefinition_ + + + + + tasknode_ + + + + + taskcontroller_ + + + +
+ + + + int8 + false + false + + + int8 + false + false + + + + + taskinstance_ + pooledactor_ + + + + + pooledactor_ + jbpm_pooledactor + id_ + + + taskinstance_ + jbpm_taskinstance + id_ + + + + + + taskinstance_ + + + + + pooledactor_ + + + +
+ + + + int8 + false + false + + + int8 + true + false + + + + + id_ + + + + + taskcontrollerdelegation_ + jbpm_delegation + id_ + + + + + + taskcontrollerdelegation_ + + + +
+ + + + int8 + false + false + + + bpchar + false + false + + + int4 + false + false + + + varchar(255) + true + false + + + text + true + false + + + varchar(255) + true + false + + + timestamp + true + false + + + timestamp + true + false + + + timestamp + true + false + + + timestamp + true + false + + + int4 + true + false + + + bool + true + false + + + bool + true + false + + + bool + true + false + + + bool + true + false + + + bool + true + false + + + int8 + true + false + + + int8 + true + false + + + int8 + true + false + + + int8 + true + false + + + int8 + true + false + + + varchar(50) + true + false + + + + + id_ + + + + + taskmgmtinstance_ + jbpm_moduleinstance + id_ + + + procinst_ + jbpm_processinstance + id_ + + + swimlaninstance_ + jbpm_swimlaneinstance + id_ + + + task_ + jbpm_task + id_ + + + token_ + jbpm_token + id_ + + + + + + swimlaninstance_ + + + + + task_ + + + + + taskmgmtinstance_ + + + + + token_ + + + + + procinst_ + + + +
+ + + + int8 + false + false + + + int4 + false + false + + + varchar(255) + true + false + + + timestamp + true + false + + + timestamp + true + false + + + timestamp + true + false + + + int4 + true + false + + + bool + true + false + + + bool + true + false + + + bool + true + false + + + varchar(255) + true + false + + + int8 + true + false + + + int8 + true + false + + + int8 + true + false + + + int8 + true + false + + + + + id_ + + + + + node_ + jbpm_node + id_ + + + processinstance_ + jbpm_processinstance + id_ + + + subprocessinstance_ + jbpm_processinstance + id_ + + + parent_ + jbpm_token + id_ + + + + + + node_ + + + + + parent_ + + + + + processinstance_ + + + + + subprocessinstance_ + + + +
+ + + + int8 + false + false + + + int4 + false + false + + + int8 + true + false + + + int8 + true + false + + + + + id_ + + + + + contextinstance_ + jbpm_moduleinstance + id_ + + + token_ + jbpm_token + id_ + + + + + + contextinstance_ + + + + + token_ + + + +
+ + + + int8 + false + false + + + varchar(255) + true + false + + + text + true + false + + + int8 + true + false + + + int8 + true + false + + + int8 + true + false + + + varchar(255) + true + false + + + int4 + true + false + + + + + id_ + + + + + to_ + jbpm_node + id_ + + + from_ + jbpm_node + id_ + + + processdefinition_ + jbpm_processdefinition + id_ + + + + + + processdefinition_ + + + + + from_ + + + + + to_ + + + +
+ + + + int8 + false + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + int8 + true + false + + + int8 + true + false + + + int4 + true + false + + + int8 + true + false + + + + + id_ + + + + + script_ + jbpm_action + id_ + + + processstate_ + jbpm_node + id_ + + + taskcontroller_ + jbpm_taskcontroller + id_ + + + + + + processstate_ + + + + + script_ + + + + + taskcontroller_ + + + +
+ + + + int8 + false + false + + + bpchar + false + false + + + int4 + false + false + + + varchar(255) + true + false + + + bpchar + true + false + + + int8 + true + false + + + int8 + true + false + + + int8 + true + false + + + int8 + true + false + + + timestamp + true + false + + + float8 + true + false + + + varchar(255) + true + false + + + int8 + true + false + + + varchar(255) + true + false + + + varchar(255) + true + false + + + int8 + true + false + + + + + id_ + + + + + bytearrayvalue_ + jbpm_bytearray + id_ + + + processinstance_ + jbpm_processinstance + id_ + + + taskinstance_ + jbpm_taskinstance + id_ + + + token_ + jbpm_token + id_ + + + tokenvariablemap_ + jbpm_tokenvariablemap + id_ + + + + + + bytearrayvalue_ + + + + + taskinstance_ + + + + + processinstance_ + + + + + token_ + + + + + tokenvariablemap_ + + + +
+
+
diff --git a/config/alfresco/dbscripts/upgrade/3.4/org.hibernate.dialect.Dialect/alter-jBPM331-CLOB-columns-to-nvarchar.sql b/config/alfresco/dbscripts/upgrade/3.4/org.hibernate.dialect.Dialect/alter-jBPM331-CLOB-columns-to-nvarchar.sql new file mode 100644 index 0000000000..e8421551f4 --- /dev/null +++ b/config/alfresco/dbscripts/upgrade/3.4/org.hibernate.dialect.Dialect/alter-jBPM331-CLOB-columns-to-nvarchar.sql @@ -0,0 +1,21 @@ +-- +-- Title: Altering CLOB columns in the jBPM 3.3.1 tables to introduce Unicode characters support for jBPM 3.3.1 +-- Database: Generic +-- Since: V3.4.8 schema 4208 +-- Author: Dmitry Velichkevich +-- +-- Please contact support@alfresco.com if you need assistance with the upgrade. +-- +-- ALF-12411: Description of workflow in 'My Tasks To Do' dashboard losing utf-8 encoding after upgrade. + +-- +-- Record script finish +-- +DELETE FROM alf_applied_patch WHERE id = 'patch.db-V3.4-alter-jBPM331-CLOB-columns-to-nvarchar'; +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-V3.4-alter-jBPM331-CLOB-columns-to-nvarchar', 'Altering CLOB columns in the jBPM 3.3.1 tables to introduce Unicode characters support for jBPM 3.3.1', + 2018, 6000, -1, 6001, null, 'UNKOWN', ${TRUE}, ${TRUE}, 'Script completed' +); diff --git a/config/alfresco/ehcache-default.xml b/config/alfresco/ehcache-default.xml index 9a74dbece9..7ba8664b29 100644 --- a/config/alfresco/ehcache-default.xml +++ b/config/alfresco/ehcache-default.xml @@ -148,7 +148,7 @@ /> - + - + diff --git a/config/alfresco/extension/video-transformation-context.xml.sample b/config/alfresco/extension/video-transformation-context.xml.sample index 093dd02920..72a080adb8 100644 --- a/config/alfresco/extension/video-transformation-context.xml.sample +++ b/config/alfresco/extension/video-transformation-context.xml.sample @@ -241,6 +241,7 @@ video/mp4
+ video/quicktime @@ -281,6 +283,7 @@ video/mp4 + diff --git a/config/alfresco/ibatis/org.hibernate.dialect.Dialect/activities-common-SqlMap.xml b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/activities-common-SqlMap.xml index f18bced93c..75546114f9 100644 --- a/config/alfresco/ibatis/org.hibernate.dialect.Dialect/activities-common-SqlMap.xml +++ b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/activities-common-SqlMap.xml @@ -95,7 +95,7 @@ having count(*) > #{maxFeedSize} ]]> - + + + + + + + + + + + - + + + + + diff --git a/config/alfresco/ibatis/org.hibernate.dialect.Dialect/content-common-SqlMap.xml b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/content-common-SqlMap.xml index acfb03d0f0..ce8a674a48 100644 --- a/config/alfresco/ibatis/org.hibernate.dialect.Dialect/content-common-SqlMap.xml +++ b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/content-common-SqlMap.xml @@ -277,8 +277,8 @@ cd.content_locale_id as content_locale_id from alf_content_data cd + join alf_node_properties np on (cd.id = np.long_value) left join alf_content_url cu on (cd.content_url_id = cu.id) - left join alf_node_properties np on (cd.id = np.long_value) where np.node_id in diff --git a/config/alfresco/ibatis/org.hibernate.dialect.Dialect/node-common-SqlMap.xml b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/node-common-SqlMap.xml index 537338fd78..9289457fe4 100644 --- a/config/alfresco/ibatis/org.hibernate.dialect.Dialect/node-common-SqlMap.xml +++ b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/node-common-SqlMap.xml @@ -924,6 +924,7 @@ join alf_node childNode on (childNode.id = assoc.child_node_id) join alf_store childStore on (childStore.id = childNode.store_id) + order by assoc.assoc_index ASC, @@ -974,6 +975,16 @@ assoc.id = #{id} + + + #{maxFeedSize} + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + delete from alf_activity_feed + where site_network = #{siteNetwork} + + + + + + + + delete from alf_activity_feed + where feed_user_id = #{feedUserId} + + + + + + + + + + + + + + + + + + + + + + + + + + #{status} + ]]> + + + + #{status} + ]]> + + + \ No newline at end of file diff --git a/config/alfresco/invitation-service-context.xml b/config/alfresco/invitation-service-context.xml index 63ea0a9079..25e91b39ae 100644 --- a/config/alfresco/invitation-service-context.xml +++ b/config/alfresco/invitation-service-context.xml @@ -6,6 +6,7 @@ + diff --git a/config/alfresco/messages/action-config.properties b/config/alfresco/messages/action-config.properties index fb5fbdee33..8ac3011581 100644 --- a/config/alfresco/messages/action-config.properties +++ b/config/alfresco/messages/action-config.properties @@ -176,6 +176,10 @@ start-workflow.workflowName.display-label=Workflow name start-workflow.endStartTask.display-label=Task start-workflow.startTaskTransition.display-label=Transition +cancel-workflow.title=Cancel Workflows +cancel-workflow.description=Cancels a list of workflow IDs. +cancel-workflow.workflow-id-list.display-label=Workflow ID List + # WCM Actions simple-avm-submit.title=Simple Direct Submit diff --git a/config/alfresco/messages/action-config_es.properties b/config/alfresco/messages/action-config_es.properties index ef4776e5a9..5a5201ff7e 100755 --- a/config/alfresco/messages/action-config_es.properties +++ b/config/alfresco/messages/action-config_es.properties @@ -176,6 +176,10 @@ start-workflow.workflowName.display-label=Nombre de flujo de trabajo start-workflow.endStartTask.display-label=Tarea start-workflow.startTaskTransition.display-label=Transici\u00f3n +cancel-workflow.title=Cancelar Flujos de trabajo +cancel-workflow.description=Cancela lista de flujos de trabajo con determinado ID. +cancel-workflow.workflow-id-list.display-label=Lista de flujos de trabajo con ID + #WCM Actions simple-avm-submit.title=Env\u00edo directo sencillo diff --git a/config/alfresco/messages/bpm-messages_es.properties b/config/alfresco/messages/bpm-messages_es.properties index 1d37de4f22..a4fd57d01d 100755 --- a/config/alfresco/messages/bpm-messages_es.properties +++ b/config/alfresco/messages/bpm-messages_es.properties @@ -55,9 +55,8 @@ bpm_businessprocessmodel.aspect.bpm_workflowPackage.description=La colecci\u00f3 #Activiti Workflow Start Task bpm_businessprocessmodel.type.bpm_activitiStartTask.title=Iniciar tarea con flujo de trabajo -bpm_businessprocessmodel.type.bpm_activitiStartTask.description=Tarea para recopilar la informaci\u00f3n necesaria para iniciar el flujo de trabajo. +bpm_businessprocessmodel.type.bpm_activitiStartTask.description=Tarea utilizada para recolectar la informaci\u00f3n requerida para iniciar el flujo de trabajo. -#Workflow Start Task bpm_businessprocessmodel.type.bpm_startTask.title=Tarea inicial de flujo de trabajo bpm_businessprocessmodel.type.bpm_startTask.description=Tarea utilizada para recolectar la informaci\u00f3n requerida para iniciar el flujo de trabajo bpm_businessprocessmodel.property.bpm_workflowDescription.title=Descripci\u00f3n diff --git a/config/alfresco/messages/categories.properties b/config/alfresco/messages/categories.properties new file mode 100644 index 0000000000..bafc3c5049 --- /dev/null +++ b/config/alfresco/messages/categories.properties @@ -0,0 +1,6 @@ +message.changeCategoryName.success=Successfully changed name of category +message.changeCategoryName.solr.success=Category update successfully queued with SOLR for changes. Please note that it may take a few moments until it is updated; you will need to refresh to see the change once it has been actioned +message.addCategory.success=Successfully added category +message.addCategory.solr.success=New category successfully queued with SOLR for addition. Please note that it may take a few moments until it is added; you will need to refresh to see the change once it has been actioned +message.removeCategory.success=Category successfully removed. +message.removeCategory.solr.success=Category deletion successfully queued with SOLR for removal. Please note that it may take a few moments until it is deleted; you will need to refresh to see the change once it has been actioned \ No newline at end of file diff --git a/config/alfresco/messages/categories_es.properties b/config/alfresco/messages/categories_es.properties new file mode 100755 index 0000000000..6172e9dd4b --- /dev/null +++ b/config/alfresco/messages/categories_es.properties @@ -0,0 +1,6 @@ +message.changeCategoryName.success=Se ha cambiado el nombre de la categor\u00eda con \u00e9xito +message.changeCategoryName.solr.success=La actualizaci\u00f3n de la categor\u00eda se ha puesto con \u00e9xito a la cola de SOLR. El proceso de actualizaci\u00f3n puede demorar unos minutos; por favor actualice la pantalla para ver el cambio en cuanto sea ejecutado. +message.addCategory.success=Se ha a\u00f1adido la categor\u00eda con \u00e9xito +message.addCategory.solr.success=La nueva categor\u00eda se ha puesto con \u00e9xito a la cola de SOLR. El proceso de actualizaci\u00f3n puede demorar unos minutos; por favor actualice la pantalla para ver el cambio en cuanto sea ejecutado. +message.removeCategory.success=Se ha eliminado la categor\u00eda con \u00e9xito +message.removeCategory.solr.success=La eliminaci\u00f3n de la categor\u00eda se ha puesto con \u00e9xito a la cola de SOLR. El proceso de eliminaci\u00f3n puede demorar unos minutos; por favor actualice la pantalla para ver el cambio en cuanto sea ejecutado. \ No newline at end of file diff --git a/config/alfresco/messages/patch-service.properties b/config/alfresco/messages/patch-service.properties index 9c48754e9f..e47f591ff3 100644 --- a/config/alfresco/messages/patch-service.properties +++ b/config/alfresco/messages/patch-service.properties @@ -453,3 +453,8 @@ patch.fixBpmPackages.description=Corrects workflow package types and association patch.fixBpmPackages.result=Patch successful. {0} packages converted. patch.fixBpmPackages.invalidBootsrapStore=Bootstrap store has not been set patch.fixBpmPackages.emptyContainer={0} node has no children + +patch.alterJBPM331CLOBcolumnsToNvarchar.description=Altering CLOB columns in the jBPM 3.3.1 tables to introduce Unicode characters support for jBPM 3.3.1 + +patch.imapUnsubscribedAspect.description=Patch to remove deprecated "imap:nonSubscribed" aspect from folders. +patch.imapUnsubscribedAspect.result.removed="imap:nonSubscribed" aspect was successfully removed from unsubscribed folders. diff --git a/config/alfresco/messages/patch-service_es.properties b/config/alfresco/messages/patch-service_es.properties index 20da4afe54..e367a84b2f 100755 --- a/config/alfresco/messages/patch-service_es.properties +++ b/config/alfresco/messages/patch-service_es.properties @@ -453,3 +453,5 @@ patch.fixBpmPackages.description=Corrects workflow package types and association patch.fixBpmPackages.result=Patch successful. {0} packages converted. patch.fixBpmPackages.invalidBootsrapStore=Bootstrap store has not been set patch.fixBpmPackages.emptyContainer={0} node has no children + +patch.alterJBPM331CLOBcolumnsToNvarchar.description=Altering CLOB columns in the jBPM 3.3.1 tables to introduce Unicode characters support for jBPM 3.3.1 diff --git a/config/alfresco/messages/system-messages.properties b/config/alfresco/messages/system-messages.properties index d2c8821837..1bf74a2bfa 100644 --- a/config/alfresco/messages/system-messages.properties +++ b/config/alfresco/messages/system-messages.properties @@ -32,6 +32,7 @@ system.schema_comp.redundant_obj.many_matches={0} redundant items? reference: {1 system.schema_comp.validation=Validation: {0} {1}="{2}" fails to match rule: {3} # Specific validator (implementations) messages... system.schema_comp.name_validator=name must match pattern ''{0}'' +system.schema_comp.schema_version_validator=version must be at least ''{0}'' # OpenOffice system.openoffice.info.connection_verified=The connection to OpenOffice has been established. diff --git a/config/alfresco/messages/system-messages_es.properties b/config/alfresco/messages/system-messages_es.properties index 5407c4e76a..8b46a201eb 100755 --- a/config/alfresco/messages/system-messages_es.properties +++ b/config/alfresco/messages/system-messages_es.properties @@ -32,6 +32,7 @@ system.schema_comp.redundant_obj.many_matches={0} redundant items? reference: {1 system.schema_comp.validation=Validation: {0} {1}="{2}" fails to match rule: {3} # Specific validator (implementations) messages... system.schema_comp.name_validator=name must match pattern ''{0}'' +system.schema_comp.schema_version_validator=version must be at least ''{0}'' # OpenOffice system.openoffice.info.connection_verified=The connection to OpenOffice has been established. diff --git a/config/alfresco/model/imapModel.xml b/config/alfresco/model/imapModel.xml index a9c5024e62..b221e081a9 100644 --- a/config/alfresco/model/imapModel.xml +++ b/config/alfresco/model/imapModel.xml @@ -138,6 +138,25 @@ + + + IMAP Preferences + + + Unsubscribed + + false + true + + + cm:folder + false + true + + + + + \ No newline at end of file diff --git a/config/alfresco/model/permissionDefinitions.xml b/config/alfresco/model/permissionDefinitions.xml index 72e0226d8a..f7a0eea6c9 100644 --- a/config/alfresco/model/permissionDefinitions.xml +++ b/config/alfresco/model/permissionDefinitions.xml @@ -299,10 +299,10 @@ - + - + @@ -339,7 +339,7 @@ - + diff --git a/config/alfresco/mt/mt-base-context.xml b/config/alfresco/mt/mt-base-context.xml index dbcdf56411..44f6268918 100644 --- a/config/alfresco/mt/mt-base-context.xml +++ b/config/alfresco/mt/mt-base-context.xml @@ -34,8 +34,6 @@ ${alfresco_user_store.adminusername} - - diff --git a/config/alfresco/patch/patch-services-context.xml b/config/alfresco/patch/patch-services-context.xml index 3a6fd15d35..8cb49e34f4 100644 --- a/config/alfresco/patch/patch-services-context.xml +++ b/config/alfresco/patch/patch-services-context.xml @@ -791,8 +791,10 @@ 89 90 + + @@ -878,9 +880,6 @@ - - - @@ -1386,7 +1385,7 @@ - + @@ -1416,9 +1415,6 @@ - - - @@ -1571,17 +1567,6 @@ - - patch.db-V3.2-Upgrade-JBPM - patch.schemaUpgradeScript.description - 0 - 2017 - 2018 - - classpath:alfresco/dbscripts/upgrade/3.2/${db.script.dialect}/jbpm-upgrade.sql - - - patch.imapFolders patch.imapFolders.description @@ -2411,6 +2396,19 @@ + + patch.db-V3.2-Upgrade-JBPM + patch.noOpPatch.description + 0 + 2017 + 2018 + + + + + + + patch.db-V3.4-AVM-rename-dupes patch.schemaUpgradeScript.description @@ -2868,11 +2866,6 @@ 0 5017 5018 - - - - - true true @@ -3008,5 +3001,53 @@ + + + patch.db-V3.4-Upgrade-JBPM + patch.schemaUpgradeScript.description + 0 + 2017 + 6001 + + + + + + + classpath:alfresco/dbscripts/upgrade/3.2/${db.script.dialect}/jbpm-upgrade.sql + + + + + patch.db-V3.4-alter-jBPM331-CLOB-columns-to-nvarchar + patch.alterJBPM331CLOBcolumnsToNvarchar.description + 0 + 6000 + 6001 + + + + + + + classpath:alfresco/dbscripts/upgrade/3.4/${db.script.dialect}/alter-jBPM331-CLOB-columns-to-nvarchar.sql + + + + patch.imapUnsubscribedAspect + patch.imapUnsubscribedAspect.description + 0 + 6001 + 6002 + + + + + + + + + + diff --git a/config/alfresco/public-services-security-context.xml b/config/alfresco/public-services-security-context.xml index b6b231c612..de227ea339 100644 --- a/config/alfresco/public-services-security-context.xml +++ b/config/alfresco/public-services-security-context.xml @@ -397,6 +397,7 @@ org.alfresco.service.cmr.repository.NodeService.getStoreArchiveNode=ACL_NODE.0.sys:base.Read org.alfresco.service.cmr.repository.NodeService.restoreNode=ACL_NODE.0.sys:base.DeleteNode,ACL_NODE.1.sys:base.CreateChildren org.alfresco.service.cmr.repository.NodeService.getChildAssocsWithoutParentAssocsOfType=ACL_NODE.0.sys:base.ReadProperties,AFTER_ACL_NODE.sys:base.ReadProperties + org.alfresco.service.cmr.repository.NodeService.countChildAssocs=ACL_NODE.0.sys:base.ReadChildren org.alfresco.service.cmr.repository.NodeService.*=ACL_DENY @@ -868,6 +869,7 @@ org.alfresco.service.cmr.security.PersonService.getPeopleContainer=ACL_ALLOW org.alfresco.service.cmr.security.PersonService.getUserNamesAreCaseSensitive=ACL_ALLOW org.alfresco.service.cmr.security.PersonService.getUserIdentifier=ACL_ALLOW + org.alfresco.service.cmr.security.PersonService.countPeople=ACL_ALLOW org.alfresco.service.cmr.security.PersonService.*=ACL_DENY diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties index e018326944..e78282f4e4 100644 --- a/config/alfresco/repository.properties +++ b/config/alfresco/repository.properties @@ -490,7 +490,6 @@ system.usages.clearBatchSize=50 system.usages.updateBatchSize=50 # Repository endpoint - used by Activity Service -repo.remote.url=http://localhost:8080/alfresco repo.remote.endpoint=/service # Create home folders as people are created (true) or create them lazily (false) @@ -637,7 +636,6 @@ authentication.ticket.validDuration=PT1H # Default NFS user mappings (empty). Note these users will be able to # authenticate through NFS without password so ensure NFS port is secure before # enabling and adding mappings -nfs.enabled=false nfs.user.mappings= nfs.user.mappings.default.uid=0 nfs.user.mappings.default.gid=0 @@ -686,6 +684,8 @@ activities.feed.max.size=100 # Feed max age (eg. 44640 mins => 31 days) activities.feed.max.ageMins=44640 +activities.feedNotifier.batchSize=200 +activities.feedNotifier.numThreads=2 # Subsystem unit test values. Will not have any effect on production servers subsystems.test.beanProp.default.longProperty=123456789123456789 diff --git a/config/alfresco/schemacomp/db-schema.xsd b/config/alfresco/schemacomp/db-schema.xsd new file mode 100644 index 0000000000..fa1276ad8d --- /dev/null +++ b/config/alfresco/schemacomp/db-schema.xsd @@ -0,0 +1,169 @@ + + + + + Schema used for describing database schemas. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/alfresco/slingshot-context.xml b/config/alfresco/slingshot-context.xml index 646b9c4a8c..f5be8b754a 100644 --- a/config/alfresco/slingshot-context.xml +++ b/config/alfresco/slingshot-context.xml @@ -15,6 +15,7 @@ 0 null + null @@ -32,6 +33,9 @@ ${vti.server.external.host} + + ${vti.server.external.protocol} + @@ -52,4 +56,4 @@ - \ No newline at end of file + diff --git a/config/alfresco/subsystems/ActivitiesFeed/default/activities-feed-context.xml b/config/alfresco/subsystems/ActivitiesFeed/default/activities-feed-context.xml index ea0f9298dc..72c3c8caaf 100644 --- a/config/alfresco/subsystems/ActivitiesFeed/default/activities-feed-context.xml +++ b/config/alfresco/subsystems/ActivitiesFeed/default/activities-feed-context.xml @@ -59,7 +59,8 @@ - + + @@ -99,22 +100,32 @@ - - - - - - - + - + + + + + + + + + + + + + + + + + admin@alfresco.com diff --git a/config/alfresco/subsystems/Search/common-search-context.xml b/config/alfresco/subsystems/Search/common-search-context.xml index 438de57e45..d7e321475a 100644 --- a/config/alfresco/subsystems/Search/common-search-context.xml +++ b/config/alfresco/subsystems/Search/common-search-context.xml @@ -46,6 +46,9 @@ SYNCHRONOUS + + + SYNCHRONOUS:TYPE:STAGING diff --git a/config/alfresco/subsystems/Search/solr/solr-search-context.xml b/config/alfresco/subsystems/Search/solr/solr-search-context.xml index 7e32cfceba..a1405f4b9b 100644 --- a/config/alfresco/subsystems/Search/solr/solr-search-context.xml +++ b/config/alfresco/subsystems/Search/solr/solr-search-context.xml @@ -40,6 +40,9 @@ + + + diff --git a/config/alfresco/subsystems/email/OutboundSMTP/outboundSMTP-context.xml b/config/alfresco/subsystems/email/OutboundSMTP/outboundSMTP-context.xml index 7b493ff77a..1310a0e18d 100755 --- a/config/alfresco/subsystems/email/OutboundSMTP/outboundSMTP-context.xml +++ b/config/alfresco/subsystems/email/OutboundSMTP/outboundSMTP-context.xml @@ -70,8 +70,11 @@ ${mail.from.default} - - ${repo.remote.url} + + ${mail.from.enabled} + + + ${mail.testmessage.send} diff --git a/config/alfresco/subsystems/email/OutboundSMTP/outboundSMTP.properties b/config/alfresco/subsystems/email/OutboundSMTP/outboundSMTP.properties index 8daca914a0..5ef2954780 100755 --- a/config/alfresco/subsystems/email/OutboundSMTP/outboundSMTP.properties +++ b/config/alfresco/subsystems/email/OutboundSMTP/outboundSMTP.properties @@ -3,10 +3,19 @@ # use these properties to configure the out-bound SMTP server. mail.host=smtp.example.com mail.port=25 + mail.username=anonymous mail.password= mail.encoding=UTF-8 + +# Default email address used for FROM if no other suitable value can found. mail.from.default=alfresco@demo.alfresco.org + +# Can the FROM field be specified as a parameter or current user or does it +# always need to be the default value - to agree with the username/password? +mail.from.enabled=true + +# Is the email protocol smtp or smtps mail.protocol=smtp # Additional Java Mail properties for SMTP protocol diff --git a/config/alfresco/subsystems/fileServers/default/file-servers.properties b/config/alfresco/subsystems/fileServers/default/file-servers.properties index b48e28a21a..536fbcbd3f 100644 --- a/config/alfresco/subsystems/fileServers/default/file-servers.properties +++ b/config/alfresco/subsystems/fileServers/default/file-servers.properties @@ -15,7 +15,8 @@ filesystem.rootPath=${protocols.rootPath} # ALF-3856 # File name patterns that trigger rename shuffle detection -filesystem.renameShufflePattern=(.*\\.tmp)|(.*\\.wbk)|(.*\\.bak)|(.*\\~) +# pattern is used by rename - tested against full path after it has been lower cased. +filesystem.renameShufflePattern=(.*[a-f0-9]{8}+$)|(.*\\.tmp$)|(.*\\.wbk$)|(.*\\.bak$)|(.*\\~$) # Should we ever set the read only flag on folders? This may cause problematic # behaviour in Windows clients. See ALF-6727. diff --git a/config/alfresco/subsystems/fileServers/default/network-protocol-context.xml b/config/alfresco/subsystems/fileServers/default/network-protocol-context.xml index 1b320769aa..6f54d94a55 100644 --- a/config/alfresco/subsystems/fileServers/default/network-protocol-context.xml +++ b/config/alfresco/subsystems/fileServers/default/network-protocol-context.xml @@ -144,6 +144,12 @@ + + + [0-9A-F]{8}+$ + 30000 + HIGH + ~WRD.*.TMP diff --git a/config/alfresco/version.properties b/config/alfresco/version.properties index dd920c0db9..957262c56a 100644 --- a/config/alfresco/version.properties +++ b/config/alfresco/version.properties @@ -19,4 +19,4 @@ version.build=@build-number@ # Schema number -version.schema=6000 +version.schema=6002 diff --git a/config/alfresco/workflow-context.xml b/config/alfresco/workflow-context.xml index 8c49278fbf..1c9d1b51eb 100644 --- a/config/alfresco/workflow-context.xml +++ b/config/alfresco/workflow-context.xml @@ -104,6 +104,11 @@ + + false + + + diff --git a/source/java/org/alfresco/cmis/dictionary/CMISObjectTypeDefinition.java b/source/java/org/alfresco/cmis/dictionary/CMISObjectTypeDefinition.java index 6486479944..be16b02717 100644 --- a/source/java/org/alfresco/cmis/dictionary/CMISObjectTypeDefinition.java +++ b/source/java/org/alfresco/cmis/dictionary/CMISObjectTypeDefinition.java @@ -1,125 +1,125 @@ -/* - * 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.cmis.dictionary; - -import java.util.ArrayList; -import java.util.Collection; - -import org.alfresco.cmis.CMISScope; -import org.alfresco.cmis.CMISTypeId; -import org.alfresco.cmis.mapping.CMISMapping; -import org.alfresco.service.cmr.dictionary.ClassDefinition; -import org.alfresco.service.cmr.dictionary.DictionaryService; -import org.alfresco.service.namespace.QName; -import org.alfresco.util.ISO9075; - - -/** - * CMIS Object Type Definition - * - * @author davidc - */ -public class CMISObjectTypeDefinition extends CMISAbstractTypeDefinition -{ - private static final long serialVersionUID = -3131505923356013430L; - - - /** - * Construct - * - * @param cmisMapping - * @param typeId - * @param cmisClassDef - */ - public CMISObjectTypeDefinition(CMISMapping cmisMapping, CMISTypeId typeId, ClassDefinition cmisClassDef, boolean isPublic) - { - this.isPublic = isPublic; - - // Object type properties - objectTypeId = typeId; - objectTypeQueryName = ISO9075.encodeSQL(cmisMapping.buildPrefixEncodedString(typeId.getQName())); - - if (cmisClassDef != null) - { - this.cmisClassDef = cmisClassDef; - displayName = (cmisClassDef.getTitle() != null) ? cmisClassDef.getTitle() : typeId.getId(); - description = cmisClassDef.getDescription() != null ? cmisClassDef.getDescription() : displayName; - QName parentQName = cmisMapping.getCmisType(cmisClassDef.getParentName()); - if (parentQName != null) - { - parentTypeId = cmisMapping.getCmisTypeId(CMISScope.OBJECT, parentQName); - } - } - - actionEvaluators = cmisMapping.getActionEvaluators(objectTypeId.getScope()); - - creatable = false; - queryable = false; - fullTextIndexed = false; - includedInSuperTypeQuery = cmisClassDef.getIncludedInSuperTypeQuery(); - controllablePolicy = false; - controllableACL = false; - } - - /** - * Create Sub Types - * - * @param cmisMapping - * @param dictionaryService - */ - /*package*/ void createSubTypes(CMISMapping cmisMapping, DictionaryService dictionaryService) - { - subTypeIds = new ArrayList(); - Collection subTypes = dictionaryService.getSubTypes(objectTypeId.getQName(), false); - for (QName subType : subTypes) - { - CMISTypeId subTypeId = cmisMapping.getCmisTypeId(subType); - if (subTypeId != null) - { - subTypeIds.add(subTypeId); - } - } - } - - /* - * (non-Javadoc) - * @see java.lang.Object#toString() - */ - @Override - public String toString() - { - StringBuilder builder = new StringBuilder(); - builder.append("CMISObjectTypeDefinition["); - builder.append("Public=").append(isPublic()).append(", "); - builder.append("ObjectTypeId=").append(getTypeId()).append(", "); - builder.append("ObjectTypeQueryName=").append(getQueryName()).append(", "); - builder.append("ObjectTypeDisplayName=").append(getDisplayName()).append(", "); - builder.append("ParentTypeId=").append(getParentType() == null ? "" : getParentType().getTypeId()).append(", "); - builder.append("Description=").append(getDescription()).append(", "); - builder.append("Creatable=").append(isCreatable()).append(", "); - builder.append("Queryable=").append(isQueryable()).append(", "); - builder.append("Controllable=").append(isControllablePolicy()).append(", "); - builder.append("IncludedInSuperTypeQuery=").append(isIncludedInSuperTypeQuery()).append(", "); - builder.append("SubTypes=").append(getSubTypes(false).size()).append(", "); - builder.append("Properties=").append(getPropertyDefinitions().size()); - builder.append("]"); - return builder.toString(); - } - -} +/* + * 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.cmis.dictionary; + +import java.util.ArrayList; +import java.util.Collection; + +import org.alfresco.cmis.CMISScope; +import org.alfresco.cmis.CMISTypeId; +import org.alfresco.cmis.mapping.CMISMapping; +import org.alfresco.service.cmr.dictionary.ClassDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.ISO9075; + + +/** + * CMIS Object Type Definition + * + * @author davidc + */ +public class CMISObjectTypeDefinition extends CMISAbstractTypeDefinition +{ + private static final long serialVersionUID = -3131505923356013430L; + + + /** + * Construct + * + * @param cmisMapping + * @param typeId + * @param cmisClassDef + */ + public CMISObjectTypeDefinition(CMISMapping cmisMapping, CMISTypeId typeId, ClassDefinition cmisClassDef, boolean isPublic) + { + this.isPublic = isPublic; + + // Object type properties + objectTypeId = typeId; + objectTypeQueryName = ISO9075.encodeSQL(cmisMapping.buildPrefixEncodedString(typeId.getQName())); + + if (cmisClassDef != null) + { + this.cmisClassDef = cmisClassDef; + displayName = (cmisClassDef.getTitle() != null) ? cmisClassDef.getTitle() : typeId.getId(); + description = cmisClassDef.getDescription() != null ? cmisClassDef.getDescription() : displayName; + QName parentQName = cmisMapping.getCmisType(cmisClassDef.getParentName()); + if (parentQName != null) + { + parentTypeId = cmisMapping.getCmisTypeId(CMISScope.OBJECT, parentQName); + } + includedInSuperTypeQuery = cmisClassDef.getIncludedInSuperTypeQuery(); + } + + actionEvaluators = cmisMapping.getActionEvaluators(objectTypeId.getScope()); + + creatable = false; + queryable = false; + fullTextIndexed = false; + controllablePolicy = false; + controllableACL = false; + } + + /** + * Create Sub Types + * + * @param cmisMapping + * @param dictionaryService + */ + /*package*/ void createSubTypes(CMISMapping cmisMapping, DictionaryService dictionaryService) + { + subTypeIds = new ArrayList(); + Collection subTypes = dictionaryService.getSubTypes(objectTypeId.getQName(), false); + for (QName subType : subTypes) + { + CMISTypeId subTypeId = cmisMapping.getCmisTypeId(subType); + if (subTypeId != null) + { + subTypeIds.add(subTypeId); + } + } + } + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() + { + StringBuilder builder = new StringBuilder(); + builder.append("CMISObjectTypeDefinition["); + builder.append("Public=").append(isPublic()).append(", "); + builder.append("ObjectTypeId=").append(getTypeId()).append(", "); + builder.append("ObjectTypeQueryName=").append(getQueryName()).append(", "); + builder.append("ObjectTypeDisplayName=").append(getDisplayName()).append(", "); + builder.append("ParentTypeId=").append(getParentType() == null ? "" : getParentType().getTypeId()).append(", "); + builder.append("Description=").append(getDescription()).append(", "); + builder.append("Creatable=").append(isCreatable()).append(", "); + builder.append("Queryable=").append(isQueryable()).append(", "); + builder.append("Controllable=").append(isControllablePolicy()).append(", "); + builder.append("IncludedInSuperTypeQuery=").append(isIncludedInSuperTypeQuery()).append(", "); + builder.append("SubTypes=").append(getSubTypes(false).size()).append(", "); + builder.append("Properties=").append(getPropertyDefinitions().size()); + builder.append("]"); + return builder.toString(); + } + +} diff --git a/source/java/org/alfresco/cmis/dictionary/CMISStrictDictionaryService.java b/source/java/org/alfresco/cmis/dictionary/CMISStrictDictionaryService.java index 05ee07c46d..5940c95965 100644 --- a/source/java/org/alfresco/cmis/dictionary/CMISStrictDictionaryService.java +++ b/source/java/org/alfresco/cmis/dictionary/CMISStrictDictionaryService.java @@ -1,126 +1,126 @@ -/* - * 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.cmis.dictionary; - -import java.util.Collection; - -import org.alfresco.cmis.CMISScope; -import org.alfresco.cmis.CMISTypeId; -import org.alfresco.cmis.mapping.CMISMapping; -import org.alfresco.model.ContentModel; -import org.alfresco.service.cmr.dictionary.AssociationDefinition; -import org.alfresco.service.cmr.dictionary.ClassDefinition; -import org.alfresco.service.namespace.QName; - - -/** - * CMIS Dictionary which provides Types that strictly conform to the CMIS specification. - * - * That is, only maps types to one of root Document, Folder, Relationship & Policy. - * - * @author davidc - */ -public class CMISStrictDictionaryService extends CMISAbstractDictionaryService -{ - /* - * (non-Javadoc) - * @see org.alfresco.cmis.dictionary.AbstractCMISDictionaryService#createDefinitions(org.alfresco.cmis.dictionary.AbstractCMISDictionaryService.DictionaryRegistry) - */ - @Override - protected void createDefinitions(DictionaryRegistry registry) - { - createTypeDefs(registry, dictionaryService.getAllTypes()); - createAssocDefs(registry, dictionaryService.getAllAssociations()); - createTypeDefs(registry, dictionaryService.getAllAspects()); - } - - /** - * Create Type Definitions - * - * @param registry - * @param classQNames - */ - private void createTypeDefs(DictionaryRegistry registry, Collection classQNames) - { - for (QName classQName : classQNames) - { - // skip items that are remapped to CMIS model - if (cmisMapping.isRemappedType(classQName)) - continue; - - // skip all items that are not mapped to CMIS model - CMISTypeId typeId = cmisMapping.getCmisTypeId(classQName); - if (typeId == null) - continue; - if (typeId.getScope() == CMISScope.RELATIONSHIP) - continue; - - // create appropriate kind of type definition - ClassDefinition classDef = dictionaryService.getClass(cmisMapping.getCmisType(typeId.getQName())); - CMISAbstractTypeDefinition objectTypeDef = null; - if (typeId.getScope() == CMISScope.DOCUMENT) - { - objectTypeDef = new CMISDocumentTypeDefinition(cmisMapping, typeId, classDef); - } - else if (typeId.getScope() == CMISScope.FOLDER) - { - boolean isSystem = dictionaryService.isSubClass(classDef.getName(), ContentModel.TYPE_SYSTEM_FOLDER); - objectTypeDef = new CMISFolderTypeDefinition(cmisMapping, typeId, classDef, isSystem); - } - else if (typeId.getScope() == CMISScope.POLICY) - { - objectTypeDef = new CMISPolicyTypeDefinition(cmisMapping, typeId, classDef); - } - else if (typeId.getScope() == CMISScope.OBJECT) - { - objectTypeDef = new CMISObjectTypeDefinition(cmisMapping, typeId, classDef, false); - } - - registry.registerTypeDefinition(objectTypeDef); - } - } - - /** - * Create Relationship Definitions - * - * @param registry - * @param classQNames - */ - private void createAssocDefs(DictionaryRegistry registry, Collection classQNames) - { - CMISTypeId typeId = cmisMapping.getCmisTypeId(CMISScope.RELATIONSHIP, CMISMapping.RELATIONSHIP_QNAME); - ClassDefinition classDef = dictionaryService.getClass(cmisMapping.getCmisType(typeId.getQName())); - CMISAbstractTypeDefinition objectTypeDef = new CMISRelationshipTypeDefinition(cmisMapping, typeId, classDef, null); - registry.registerTypeDefinition(objectTypeDef); - - for (QName classQName : classQNames) - { - if (!cmisMapping.isValidCmisRelationship(classQName)) - continue; - - // create appropriate kind of type definition - typeId = cmisMapping.getCmisTypeId(CMISScope.RELATIONSHIP, classQName); - AssociationDefinition assocDef = dictionaryService.getAssociation(classQName); - objectTypeDef = new CMISRelationshipTypeDefinition(cmisMapping, typeId, null, assocDef); - - registry.registerTypeDefinition(objectTypeDef); - } - } - -} +/* + * 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.cmis.dictionary; + +import java.util.Collection; + +import org.alfresco.cmis.CMISScope; +import org.alfresco.cmis.CMISTypeId; +import org.alfresco.cmis.mapping.CMISMapping; +import org.alfresco.model.ContentModel; +import org.alfresco.service.cmr.dictionary.AssociationDefinition; +import org.alfresco.service.cmr.dictionary.ClassDefinition; +import org.alfresco.service.namespace.QName; + + +/** + * CMIS Dictionary which provides Types that strictly conform to the CMIS specification. + * + * That is, only maps types to one of root Document, Folder, Relationship & Policy. + * + * @author davidc + */ +public class CMISStrictDictionaryService extends CMISAbstractDictionaryService +{ + /* + * (non-Javadoc) + * @see org.alfresco.cmis.dictionary.AbstractCMISDictionaryService#createDefinitions(org.alfresco.cmis.dictionary.AbstractCMISDictionaryService.DictionaryRegistry) + */ + @Override + protected void createDefinitions(DictionaryRegistry registry) + { + createTypeDefs(registry, dictionaryService.getAllTypes()); + createAssocDefs(registry, dictionaryService.getAllAssociations()); + createTypeDefs(registry, dictionaryService.getAllAspects()); + } + + /** + * Create Type Definitions + * + * @param registry + * @param classQNames + */ + private void createTypeDefs(DictionaryRegistry registry, Collection classQNames) + { + for (QName classQName : classQNames) + { + // skip items that are remapped to CMIS model + if (cmisMapping.isRemappedType(classQName)) + continue; + + // skip all items that are not mapped to CMIS model + CMISTypeId typeId = cmisMapping.getCmisTypeId(classQName); + if (typeId == null) + continue; + if (typeId.getScope() == CMISScope.RELATIONSHIP || typeId.getScope() == CMISScope.UNKNOWN ) + continue; + + // create appropriate kind of type definition + ClassDefinition classDef = dictionaryService.getClass(cmisMapping.getCmisType(typeId.getQName())); + CMISAbstractTypeDefinition objectTypeDef = null; + if (typeId.getScope() == CMISScope.DOCUMENT) + { + objectTypeDef = new CMISDocumentTypeDefinition(cmisMapping, typeId, classDef); + } + else if (typeId.getScope() == CMISScope.FOLDER) + { + boolean isSystem = dictionaryService.isSubClass(classDef.getName(), ContentModel.TYPE_SYSTEM_FOLDER); + objectTypeDef = new CMISFolderTypeDefinition(cmisMapping, typeId, classDef, isSystem); + } + else if (typeId.getScope() == CMISScope.POLICY) + { + objectTypeDef = new CMISPolicyTypeDefinition(cmisMapping, typeId, classDef); + } + else if (typeId.getScope() == CMISScope.OBJECT) + { + objectTypeDef = new CMISObjectTypeDefinition(cmisMapping, typeId, classDef, false); + } + + registry.registerTypeDefinition(objectTypeDef); + } + } + + /** + * Create Relationship Definitions + * + * @param registry + * @param classQNames + */ + private void createAssocDefs(DictionaryRegistry registry, Collection classQNames) + { + CMISTypeId typeId = cmisMapping.getCmisTypeId(CMISScope.RELATIONSHIP, CMISMapping.RELATIONSHIP_QNAME); + ClassDefinition classDef = dictionaryService.getClass(cmisMapping.getCmisType(typeId.getQName())); + CMISAbstractTypeDefinition objectTypeDef = new CMISRelationshipTypeDefinition(cmisMapping, typeId, classDef, null); + registry.registerTypeDefinition(objectTypeDef); + + for (QName classQName : classQNames) + { + if (!cmisMapping.isValidCmisRelationship(classQName)) + continue; + + // create appropriate kind of type definition + typeId = cmisMapping.getCmisTypeId(CMISScope.RELATIONSHIP, classQName); + AssociationDefinition assocDef = dictionaryService.getAssociation(classQName); + objectTypeDef = new CMISRelationshipTypeDefinition(cmisMapping, typeId, null, assocDef); + + registry.registerTypeDefinition(objectTypeDef); + } + } + +} diff --git a/source/java/org/alfresco/cmis/mapping/CMISServicesImpl.java b/source/java/org/alfresco/cmis/mapping/CMISServicesImpl.java index 270d04eae3..d75280b113 100644 --- a/source/java/org/alfresco/cmis/mapping/CMISServicesImpl.java +++ b/source/java/org/alfresco/cmis/mapping/CMISServicesImpl.java @@ -1668,19 +1668,7 @@ public class CMISServicesImpl implements CMISServices, ApplicationContextAware, } versionService.deleteVersionHistory(nodeRef); } - - // Remove not primary parent associations - List childAssociations = nodeService.getParentAssocs(nodeRef); - if (childAssociations != null) - { - for (ChildAssociationRef childAssoc : childAssociations) - { - if (!childAssoc.isPrimary()) - { - nodeService.removeChildAssociation(childAssoc); - } - } - } + // Attempt to delete the node nodeService.deleteNode(nodeRef); } diff --git a/source/java/org/alfresco/cmis/mapping/FixedValueProperty.java b/source/java/org/alfresco/cmis/mapping/FixedValueProperty.java index bd95e45200..1936186fc3 100644 --- a/source/java/org/alfresco/cmis/mapping/FixedValueProperty.java +++ b/source/java/org/alfresco/cmis/mapping/FixedValueProperty.java @@ -45,19 +45,19 @@ import org.apache.lucene.search.TermQuery; */ public class FixedValueProperty extends AbstractProperty { - private Serializable value; + private Serializable fixedValue; /** * Construct * * @param serviceRegistry * @param propertyName - * @param value + * @param fixedValue */ - public FixedValueProperty(ServiceRegistry serviceRegistry, String propertyName, Serializable value) + public FixedValueProperty(ServiceRegistry serviceRegistry, String propertyName, Serializable fixedValue) { super(serviceRegistry, propertyName); - this.value = value; + this.fixedValue = fixedValue; } /* @@ -66,7 +66,7 @@ public class FixedValueProperty extends AbstractProperty */ public Serializable getValue(NodeRef nodeRef) { - return value; + return fixedValue; } /* @@ -75,7 +75,7 @@ public class FixedValueProperty extends AbstractProperty */ public Serializable getValue(AssociationRef assocRef) { - return value; + return fixedValue; } /* @@ -84,7 +84,7 @@ public class FixedValueProperty extends AbstractProperty */ public Query buildLuceneEquality(AbstractLuceneQueryParser lqp, Serializable value, PredicateMode mode, LuceneFunction luceneFunction) throws ParseException { - if (EqualsHelper.nullSafeEquals(value, value)) + if (EqualsHelper.nullSafeEquals(value, fixedValue)) { return new MatchAllDocsQuery(); } @@ -102,7 +102,7 @@ public class FixedValueProperty extends AbstractProperty { if (not) { - if (value == null) + if (fixedValue == null) { return new MatchAllDocsQuery(); } @@ -113,7 +113,7 @@ public class FixedValueProperty extends AbstractProperty } else { - if (value == null) + if (fixedValue == null) { return new TermQuery(new Term("NO_TOKENS", "__")); } @@ -135,7 +135,7 @@ public class FixedValueProperty extends AbstractProperty if (value instanceof Comparable) { Comparable comparable = (Comparable) value; - if (comparable.compareTo(value) > 0) + if (comparable.compareTo(fixedValue) > 0) { return new MatchAllDocsQuery(); } @@ -160,7 +160,7 @@ public class FixedValueProperty extends AbstractProperty if (value instanceof Comparable) { Comparable comparable = (Comparable) value; - if (comparable.compareTo(value) >= 0) + if (comparable.compareTo(fixedValue) >= 0) { return new MatchAllDocsQuery(); } @@ -184,7 +184,7 @@ public class FixedValueProperty extends AbstractProperty boolean in = false; for (Serializable value : values) { - if (EqualsHelper.nullSafeEquals(value, value)) + if (EqualsHelper.nullSafeEquals(value, fixedValue)) { in = true; break; @@ -207,7 +207,7 @@ public class FixedValueProperty extends AbstractProperty */ public Query buildLuceneInequality(AbstractLuceneQueryParser lqp, Serializable value, PredicateMode mode, LuceneFunction luceneFunction) throws ParseException { - if (!EqualsHelper.nullSafeEquals(value, value)) + if (!EqualsHelper.nullSafeEquals(value, fixedValue)) { return new MatchAllDocsQuery(); } @@ -227,7 +227,7 @@ public class FixedValueProperty extends AbstractProperty if (value instanceof Comparable) { Comparable comparable = (Comparable) value; - if (comparable.compareTo(value) < 0) + if (comparable.compareTo(fixedValue) < 0) { return new MatchAllDocsQuery(); } @@ -252,7 +252,7 @@ public class FixedValueProperty extends AbstractProperty if (value instanceof Comparable) { Comparable comparable = (Comparable) value; - if (comparable.compareTo(value) <= 0) + if (comparable.compareTo(fixedValue) <= 0) { return new MatchAllDocsQuery(); } @@ -281,7 +281,7 @@ public class FixedValueProperty extends AbstractProperty String asString = DefaultTypeConverter.INSTANCE.convert(String.class, converted); String regExpression = SearchLanguageConversion.convertSQLLikeToRegex(asString); Pattern pattern = Pattern.compile(regExpression); - String target = DefaultTypeConverter.INSTANCE.convert(String.class, value); + String target = DefaultTypeConverter.INSTANCE.convert(String.class, fixedValue); Matcher matcher = pattern.matcher(target); if (matcher.matches()) { diff --git a/source/java/org/alfresco/email/server/EmailServiceImpl.java b/source/java/org/alfresco/email/server/EmailServiceImpl.java index f7059b2ce8..57da40aca4 100644 --- a/source/java/org/alfresco/email/server/EmailServiceImpl.java +++ b/source/java/org/alfresco/email/server/EmailServiceImpl.java @@ -20,6 +20,8 @@ package org.alfresco.email.server; import java.util.Map; +import javax.mail.internet.InternetAddress; + import org.alfresco.email.server.handler.EmailMessageHandler; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; @@ -197,10 +199,14 @@ public class EmailServiceImpl implements EmailService { if(logger.isDebugEnabled()) { - logger.debug("unable to find user for from: " + delivery.getFrom() + "trying message next"); + logger.debug("unable to find user for from: " + delivery.getFrom() + ",trying message.from next"); } userName = getUsername(message.getFrom()); } + if(logger.isDebugEnabled()) + { + logger.debug("userName = : " + userName); + } if (userName == null) { @@ -330,6 +336,10 @@ public class EmailServiceImpl implements EmailService */ private NodeRef getTargetNode(String recipient) { + if (logger.isDebugEnabled()) + { + logger.debug("getTarget node for" + recipient); + } if (recipient == null || recipient.length() == 0) { throw new EmailMessageException(ERR_INVALID_NODE_ADDRESS, recipient); @@ -391,11 +401,17 @@ public class EmailServiceImpl implements EmailService { String userName = null; - if(from == null) + if(from == null || from.length()==0) { return null; } + if(logger.isDebugEnabled()) + { + logger.debug("getUsername from: " + from); + } + + StoreRef storeRef = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore"); String query = "TYPE:cm\\:person +@cm\\:email:\"" + from + "\""; @@ -414,6 +430,11 @@ public class EmailServiceImpl implements EmailService userName = DefaultTypeConverter.INSTANCE.convert( String.class, nodeService.getProperty(userNode, ContentModel.PROP_USERNAME)); + + if(logger.isDebugEnabled()) + { + logger.debug("found username: " + userName); + } } else { diff --git a/source/java/org/alfresco/email/server/EmailServiceImplTest.java b/source/java/org/alfresco/email/server/EmailServiceImplTest.java index 6613fecbff..e03c65163b 100644 --- a/source/java/org/alfresco/email/server/EmailServiceImplTest.java +++ b/source/java/org/alfresco/email/server/EmailServiceImplTest.java @@ -37,6 +37,7 @@ import junit.framework.TestCase; import org.alfresco.email.server.handler.FolderEmailMessageHandler; import org.alfresco.email.server.impl.subetha.SubethaEmailMessage; import org.alfresco.model.ContentModel; +import org.alfresco.model.ForumModel; import org.alfresco.repo.management.subsystems.ChildApplicationContextFactory; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.service.cmr.email.EmailDelivery; @@ -248,7 +249,8 @@ public class EmailServiceImplTest extends TestCase /** * Step 3 * - * From with < name@ domain > format + * message.from From with "name" < name@ domain > format + * SMTP.FROM="dummy" * * Send From the test user to the test user's home */ @@ -278,13 +280,54 @@ public class EmailServiceImplTest extends TestCase SubethaEmailMessage m = new SubethaEmailMessage(is); - EmailDelivery delivery = new EmailDelivery(to, from, null); + EmailDelivery delivery = new EmailDelivery(to, "dummy", null); + + emailService.importMessage(delivery,m); + } + + /** + * Step 4 + * + * From with "name" < name@ domain > format + * + * Send From the test user to the test user's home + */ + { + logger.debug("Step 4"); + + String from = " \"Joe Bloggs\" <" + TEST_EMAIL + ">"; + String to = testUserHomeDBID; + String content = "hello world"; + + Session sess = Session.getDefaultInstance(new Properties()); + assertNotNull("sess is null", sess); + SMTPMessage msg = new SMTPMessage(sess); + InternetAddress[] toa = { new InternetAddress(to) }; + + msg.setFrom(new InternetAddress(from)); + msg.setRecipients(Message.RecipientType.TO, toa); + msg.setSubject("JavaMail APIs transport.java Test"); + msg.setContent(content, "text/plain"); + + StringBuffer sb = new StringBuffer(); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + msg.writeTo(System.out); + msg.writeTo(bos); + InputStream is = new StringInputStream(bos.toString()); + assertNotNull("is is null", is); + + SubethaEmailMessage m = new SubethaEmailMessage(is); + + InternetAddress a = new InternetAddress(from); + String x = a.getAddress(); + + EmailDelivery delivery = new EmailDelivery(to, x, null); emailService.importMessage(delivery,m); } // /** -// * Step 4 +// * Step 5 // * // * From with format // * @@ -375,6 +418,7 @@ public class EmailServiceImplTest extends TestCase String to = testUserHomeDBID; String content = "hello world"; + { Session sess = Session.getDefaultInstance(new Properties()); assertNotNull("sess is null", sess); SMTPMessage msg = new SMTPMessage(sess); @@ -395,6 +439,56 @@ public class EmailServiceImplTest extends TestCase EmailDelivery delivery = new EmailDelivery(to, from, null); emailService.importMessage(delivery, m); + } + + // Check import with subject containing some "illegal chars" + { + Session sess = Session.getDefaultInstance(new Properties()); + assertNotNull("sess is null", sess); + SMTPMessage msg = new SMTPMessage(sess); + InternetAddress[] toa = { new InternetAddress(to) }; + + msg.setFrom(new InternetAddress(TEST_EMAIL)); + msg.setRecipients(Message.RecipientType.TO, toa); + msg.setSubject("Illegal<>!*/\\.txt"); + msg.setContent(content, "text/plain"); + + StringBuffer sb = new StringBuffer(); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + msg.writeTo(bos); + InputStream is = new StringInputStream(bos.toString()); + assertNotNull("is is null", is); + + SubethaEmailMessage m = new SubethaEmailMessage(is); + EmailDelivery delivery = new EmailDelivery(to, from, null); + + emailService.importMessage(delivery, m); + } + + // Check with null subject + { + Session sess = Session.getDefaultInstance(new Properties()); + assertNotNull("sess is null", sess); + SMTPMessage msg = new SMTPMessage(sess); + InternetAddress[] toa = { new InternetAddress(to) }; + + msg.setFrom(new InternetAddress(TEST_EMAIL)); + msg.setRecipients(Message.RecipientType.TO, toa); + //msg.setSubject(); + msg.setContent(content, "text/plain"); + + StringBuffer sb = new StringBuffer(); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + msg.writeTo(bos); + InputStream is = new StringInputStream(bos.toString()); + assertNotNull("is is null", is); + + SubethaEmailMessage m = new SubethaEmailMessage(is); + EmailDelivery delivery = new EmailDelivery(to, from, null); + + emailService.importMessage(delivery, m); + } + } @@ -558,6 +652,113 @@ public class EmailServiceImplTest extends TestCase } + + /** + * ALF-12297 + * + * Test messages being sent to a cm:content node + */ + public void testMessagesToDocument() throws Exception + { + logger.debug("Start testMessagesToDocument"); + + String TEST_EMAIL="buffy@sunnydale.high"; + + String TEST_SUBJECT="Practical Bee Keeping"; + + String TEST_LONG_SUBJECT = "This is a very very long name in particular it is greater than eitghty six characters which was a problem explored in ALF-9544"; + + + // TODO Investigate why setting PROP_EMAIL on createPerson does not work. + NodeRef person = personService.getPerson(TEST_USER); + if(person == null) + { + logger.debug("new person created"); + Map props = new HashMap(); + props.put(ContentModel.PROP_USERNAME, TEST_USER); + props.put(ContentModel.PROP_EMAIL, TEST_EMAIL); + person = personService.createPerson(props); + } + nodeService.setProperty(person, ContentModel.PROP_EMAIL, TEST_EMAIL); + + Set auths = authorityService.getContainedAuthorities(null, "GROUP_EMAIL_CONTRIBUTORS", true); + if(!auths.contains(TEST_USER)) + { + authorityService.addAuthority("GROUP_EMAIL_CONTRIBUTORS", TEST_USER); + } + + String companyHomePathInStore = "/app:company_home"; + String storePath = "workspace://SpacesStore"; + StoreRef storeRef = new StoreRef(storePath); + + NodeRef storeRootNodeRef = nodeService.getRootNode(storeRef); + List nodeRefs = searchService.selectNodes(storeRootNodeRef, companyHomePathInStore, null, namespaceService, false); + NodeRef companyHomeNodeRef = nodeRefs.get(0); + assertNotNull("company home is null", companyHomeNodeRef); + String companyHomeDBID = ((Long)nodeService.getProperty(companyHomeNodeRef, ContentModel.PROP_NODE_DBID)).toString() + "@Alfresco.com"; + // String testUserDBID = ((Long)nodeService.getProperty(person, ContentModel.PROP_NODE_DBID)).toString() + "@Alfresco.com"; + NodeRef testUserHomeFolder = (NodeRef)nodeService.getProperty(person, ContentModel.PROP_HOMEFOLDER); + assertNotNull("testUserHomeFolder is null", testUserHomeFolder); +// String testUserHomeDBID = ((Long)nodeService.getProperty(testUserHomeFolder, ContentModel.PROP_NODE_DBID)).toString() + "@Alfresco.com"; + + // Clean up old messages in test folder + List assocs = nodeService.getChildAssocs(testUserHomeFolder, ContentModel.ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL); + for(ChildAssociationRef assoc : assocs) + { + nodeService.deleteNode(assoc.getChildRef()); + } + + + Map properties = new HashMap(); + properties.put(ContentModel.PROP_NAME, "bees"); + properties.put(ContentModel.PROP_DESCRIPTION, "bees - test doc for email tests"); + ChildAssociationRef testDoc = nodeService.createNode(testUserHomeFolder, ContentModel.ASSOC_CONTAINS, QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "bees"), ContentModel.TYPE_CONTENT, properties); + NodeRef testDocNodeRef = testDoc.getChildRef(); + + String testDocDBID = ((Long)nodeService.getProperty(testDocNodeRef, ContentModel.PROP_NODE_DBID)).toString(); + + /** + * Send From the test user TEST_EMAIL to the test user's home + */ + String from = TEST_EMAIL; + String to = testDocDBID + "@alfresco.com"; + String content = "hello world"; + + Session sess = Session.getDefaultInstance(new Properties()); + assertNotNull("sess is null", sess); + SMTPMessage msg = new SMTPMessage(sess); + InternetAddress[] toa = { new InternetAddress(to) }; + + msg.setFrom(new InternetAddress(TEST_EMAIL)); + msg.setRecipients(Message.RecipientType.TO, toa); + msg.setSubject(TEST_SUBJECT); + msg.setContent(content, "text/plain"); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + msg.writeTo(bos); + InputStream is = new StringInputStream(bos.toString()); + assertNotNull("is is null", is); + + SubethaEmailMessage m = new SubethaEmailMessage(is); + + /** + * Turn on overwriteDuplicates + */ + logger.debug("Step 1: send an email to a doc"); + + EmailDelivery delivery = new EmailDelivery(to, from, null); + + emailService.importMessage(delivery, m); + + assertTrue(nodeService.hasAspect(testDocNodeRef, ForumModel.ASPECT_DISCUSSABLE)); + + + + } // end of test sending to cm:content node + + + + /** * The Email contributors authority controls who can add email. * diff --git a/source/java/org/alfresco/email/server/handler/AbstractEmailMessageHandler.java b/source/java/org/alfresco/email/server/handler/AbstractEmailMessageHandler.java index 74067f9460..dc39ae56af 100644 --- a/source/java/org/alfresco/email/server/handler/AbstractEmailMessageHandler.java +++ b/source/java/org/alfresco/email/server/handler/AbstractEmailMessageHandler.java @@ -277,15 +277,15 @@ public abstract class AbstractEmailMessageHandler implements EmailMessageHandler { NodeRef childNodeRef = null; - String workingName = name; + String workingName = encodeSubject(name); // Need to work out a new safe name. - String baseName = FilenameUtils.getBaseName(name); - String extension = FilenameUtils.getExtension(name); + String baseName = FilenameUtils.getBaseName(workingName); + String extension = FilenameUtils.getExtension(workingName); if(logger.isDebugEnabled()) { - logger.debug("addContentNode name:" + name); + logger.debug("addContentNode name:" + workingName); } for(int counter = 1; counter < 10000; counter++) @@ -300,7 +300,7 @@ public abstract class AbstractEmailMessageHandler implements EmailMessageHandler { if(logger.isDebugEnabled()) { - logger.debug("overwriting existing node :" + name); + logger.debug("overwriting existing node :" + workingName); } childNodeRef=childNodeRefs.get(0).getChildRef(); @@ -457,4 +457,26 @@ public abstract class AbstractEmailMessageHandler implements EmailMessageHandler writeContent(attachmentNode, contentIs, mimetype, encoding); } } + + /** + * Replaces characters \/*|:"<>?. on their hex values. Subject field is used as name of the content, so we need to replace characters that are forbidden in content names. + * + * @param subject String representing subject + * @return Encoded string + */ + // MER Removed . * , { ".", "%2e" } + public static String encodeSubject(String subject) + { + String result = subject.trim(); + String[][] s = new String[][] { { "\\", "%5c" }, { "/", "%2f" }, { "*", "%2a" }, { "|", "%7c" }, { ":", "%3a" }, { "\"", "%22" }, { "<", "%3c" }, { ">", "%3e" }, + { "?", "%3f" }}; + + for (int i = 0; i < s.length; i++) + { + result = result.replace(s[i][0], s[i][1]); + } + + return result; + } + } diff --git a/source/java/org/alfresco/email/server/handler/AbstractForumEmailMessageHandler.java b/source/java/org/alfresco/email/server/handler/AbstractForumEmailMessageHandler.java index 4e1c36dfdf..244d7d925d 100644 --- a/source/java/org/alfresco/email/server/handler/AbstractForumEmailMessageHandler.java +++ b/source/java/org/alfresco/email/server/handler/AbstractForumEmailMessageHandler.java @@ -108,21 +108,12 @@ public abstract class AbstractForumEmailMessageHandler extends AbstractEmailMess * @param subject String for search * @return Reference to found node or null if node isn't found */ - protected NodeRef getTopicNode(NodeRef nodeRef, String subject) + protected NodeRef getTopicNode(NodeRef nodeRef, String name) { - List assocRefList = getNodeService().getChildAssocs(nodeRef); - Iterator assocRefIter = assocRefList.iterator(); - - while (assocRefIter.hasNext()) - { - - ChildAssociationRef assocRef = assocRefIter.next(); - if (assocRef.getQName().getLocalName().equals(subject)) - { - return assocRef.getChildRef(); - } - } - return null; + String workingName = encodeSubject(name); + + NodeRef ret = getNodeService().getChildByName(nodeRef, ContentModel.ASSOC_CONTAINS, workingName); + return ret; } /** @@ -134,17 +125,19 @@ public abstract class AbstractForumEmailMessageHandler extends AbstractEmailMess */ protected NodeRef addTopicNode(NodeRef parentNode, String name) { + String workingName = encodeSubject(name); + NodeService nodeService = getNodeService(); Map properties = new HashMap(1); - properties.put(ContentModel.PROP_NAME, name); + properties.put(ContentModel.PROP_NAME, workingName); - NodeRef topicNode = nodeService.getChildByName(parentNode, ContentModel.ASSOC_CONTAINS, name); + NodeRef topicNode = nodeService.getChildByName(parentNode, ContentModel.ASSOC_CONTAINS, workingName); if (topicNode == null) { ChildAssociationRef association = nodeService.createNode( parentNode, ContentModel.ASSOC_CONTAINS, - QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, name), + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, workingName), ForumModel.TYPE_TOPIC, properties); topicNode = association.getChildRef(); @@ -157,5 +150,4 @@ public abstract class AbstractForumEmailMessageHandler extends AbstractEmailMess return topicNode; } - } diff --git a/source/java/org/alfresco/email/server/handler/DocumentEmailMessageHandler.java b/source/java/org/alfresco/email/server/handler/DocumentEmailMessageHandler.java index f11f45b9df..7767e0d045 100644 --- a/source/java/org/alfresco/email/server/handler/DocumentEmailMessageHandler.java +++ b/source/java/org/alfresco/email/server/handler/DocumentEmailMessageHandler.java @@ -24,6 +24,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import org.alfresco.email.server.EmailServiceImpl; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ApplicationModel; import org.alfresco.model.ContentModel; @@ -34,6 +35,8 @@ import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.namespace.QName; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; /** * Handler implementation address to document node. @@ -43,13 +46,15 @@ import org.alfresco.service.namespace.QName; */ public class DocumentEmailMessageHandler extends AbstractForumEmailMessageHandler { + private static Log logger = LogFactory.getLog(DocumentEmailMessageHandler.class); + private static final String forumNodeName = "EmailForum"; public void processMessage(NodeRef contentNodeRef, EmailMessage message) { - String messageSubject; + String messageSubject = message.getSubject(); - if (message.getSubject() != null) + if (messageSubject != null && messageSubject.length() > 0) { messageSubject = message.getSubject(); } @@ -57,6 +62,10 @@ public class DocumentEmailMessageHandler extends AbstractForumEmailMessageHandle { messageSubject = "EMPTY_SUBJECT_" + System.currentTimeMillis(); } + if(logger.isDebugEnabled()) + { + logger.debug("process message:" + messageSubject); + } QName nodeTypeQName = getNodeService().getType(contentNodeRef); @@ -70,6 +79,7 @@ public class DocumentEmailMessageHandler extends AbstractForumEmailMessageHandle if (forumNode == null) { + logger.debug("adding new forum node"); forumNode = addForumNode(contentNodeRef); } @@ -78,10 +88,12 @@ public class DocumentEmailMessageHandler extends AbstractForumEmailMessageHandle if (topicNodeRef == null) { + logger.debug("adding new topic node"); topicNodeRef = addTopicNode(forumNode, messageSubject); } // Create the post + logger.debug("add a post to the topic"); NodeRef postNodeRef = addPostNode(topicNodeRef, message); // Add attachments @@ -104,11 +116,12 @@ public class DocumentEmailMessageHandler extends AbstractForumEmailMessageHandle private NodeRef addForumNode(NodeRef nodeRef) { NodeService nodeService = getNodeService(); - //Add discussable aspect to content node - if (!nodeService.hasAspect(nodeRef, ForumModel.ASPECT_DISCUSSABLE)) - { - nodeService.addAspect(nodeRef, ForumModel.ASPECT_DISCUSSABLE, null); - } + +// //Add discussable aspect to content node +// if (!nodeService.hasAspect(nodeRef, ForumModel.ASPECT_DISCUSSABLE)) +// { +// nodeService.addAspect(nodeRef, ForumModel.ASPECT_DISCUSSABLE, null); +// } //Create forum node and associate it with content node Map properties = new HashMap(1); diff --git a/source/java/org/alfresco/email/server/impl/subetha/SubethaEmailMessage.java b/source/java/org/alfresco/email/server/impl/subetha/SubethaEmailMessage.java index c2a788f291..d47fa97849 100644 --- a/source/java/org/alfresco/email/server/impl/subetha/SubethaEmailMessage.java +++ b/source/java/org/alfresco/email/server/impl/subetha/SubethaEmailMessage.java @@ -200,16 +200,17 @@ public class SubethaEmailMessage implements EmailMessage try { - subject = encodeSubject(mimeMessage.getSubject()); + subject = mimeMessage.getSubject(); + //subject = encodeSubject(mimeMessage.getSubject()); } catch (MessagingException e) { throw new EmailMessageException(ERR_EXTRACTING_SUBJECT, e.getMessage()); } - if (subject == null) - { - subject = ""; // Just anti-null stub :) - } + //if (subject == null) + //{ + // subject = ""; // Just anti-null stub :) + //} try { @@ -462,25 +463,25 @@ public class SubethaEmailMessage implements EmailMessage return attachments; } - /** - * Replaces characters \/*|:"<>?. on their hex values. Subject field is used as name of the content, so we need to replace characters that are forbidden in content names. - * - * @param subject String representing subject - * @return Encoded string - */ - // MER Removed . * , { ".", "%2e" } - static private String encodeSubject(String subject) - { - String result = subject.trim(); - String[][] s = new String[][] { { "\\", "%5c" }, { "/", "%2f" }, { "*", "%2a" }, { "|", "%7c" }, { ":", "%3a" }, { "\"", "%22" }, { "<", "%3c" }, { ">", "%3e" }, - { "?", "%3f" }}; - - for (int i = 0; i < s.length; i++) - { - result = result.replace(s[i][0], s[i][1]); - } - - return result; - } +// /** +// * Replaces characters \/*|:"<>?. on their hex values. Subject field is used as name of the content, so we need to replace characters that are forbidden in content names. +// * +// * @param subject String representing subject +// * @return Encoded string +// */ +// // MER Removed . * , { ".", "%2e" } +// static private String encodeSubject(String subject) +// { +// String result = subject.trim(); +// String[][] s = new String[][] { { "\\", "%5c" }, { "/", "%2f" }, { "*", "%2a" }, { "|", "%7c" }, { ":", "%3a" }, { "\"", "%22" }, { "<", "%3c" }, { ">", "%3e" }, +// { "?", "%3f" }}; +// +// for (int i = 0; i < s.length; i++) +// { +// result = result.replace(s[i][0], s[i][1]); +// } +// +// return result; +// } } diff --git a/source/java/org/alfresco/email/server/impl/subetha/SubethaEmailServer.java b/source/java/org/alfresco/email/server/impl/subetha/SubethaEmailServer.java index 2fc91cdb93..57261cb075 100644 --- a/source/java/org/alfresco/email/server/impl/subetha/SubethaEmailServer.java +++ b/source/java/org/alfresco/email/server/impl/subetha/SubethaEmailServer.java @@ -64,6 +64,8 @@ public class SubethaEmailServer extends EmailServer public void startup() { serverImpl = new SMTPServer(new HandlerFactory()); + + // MER - May need to override SMTPServer.createSSLSocket to specify non default keystore. serverImpl.setPort(getPort()); serverImpl.setHostName(getDomain()); serverImpl.setMaxConnections(getMaxConnections()); @@ -95,10 +97,15 @@ public class SubethaEmailServer extends EmailServer public void login(String username, String password) throws LoginFailedException { - if(authenticateUserNamePassword(username, password.toCharArray())) + if(!authenticateUserNamePassword(username, password.toCharArray())) { throw new LoginFailedException("unable to log on"); - } + } + if(logger.isDebugEnabled()) + { + logger.debug("User authenticated successfully" + username); + } + // here if authentication successful. } } diff --git a/source/java/org/alfresco/filesys/alfresco/PseudoFileImpl.java b/source/java/org/alfresco/filesys/alfresco/PseudoFileImpl.java index 5c16a2fe50..6da41ff095 100644 --- a/source/java/org/alfresco/filesys/alfresco/PseudoFileImpl.java +++ b/source/java/org/alfresco/filesys/alfresco/PseudoFileImpl.java @@ -170,10 +170,6 @@ public class PseudoFileImpl implements PseudoFileInterface if ( isCIFS && ctx.numberOfDesktopActions() > 0 && path.equals(FileName.DOS_SEPERATOR_STR) == false) { - // If the file state is null create a file state for the path - - if ( fstate == null) - ctx.getStateCache().findFileState( path, true); // Add the desktop action pseudo files diff --git a/source/java/org/alfresco/filesys/avm/AVMDiskDriver.java b/source/java/org/alfresco/filesys/avm/AVMDiskDriver.java index 66b17dcf54..f3f7923d1a 100644 --- a/source/java/org/alfresco/filesys/avm/AVMDiskDriver.java +++ b/source/java/org/alfresco/filesys/avm/AVMDiskDriver.java @@ -675,8 +675,8 @@ public class AVMDiskDriver extends AlfrescoTxDiskDriver implements DiskInterface } // Commit the transaction - - tx.commit(); + if (tx != null) + tx.commit(); tx = null; } catch (Exception ex) @@ -745,7 +745,8 @@ public class AVMDiskDriver extends AlfrescoTxDiskDriver implements DiskInterface // Commit the transaction - tx.commit(); + if (tx != null) + tx.commit(); tx = null; } catch (Exception ex) diff --git a/source/java/org/alfresco/filesys/avm/WebProjectStorePseudoFile.java b/source/java/org/alfresco/filesys/avm/WebProjectStorePseudoFile.java index 05c6f0b1b7..21a3799bb0 100644 --- a/source/java/org/alfresco/filesys/avm/WebProjectStorePseudoFile.java +++ b/source/java/org/alfresco/filesys/avm/WebProjectStorePseudoFile.java @@ -165,7 +165,7 @@ public class WebProjectStorePseudoFile extends StorePseudoFile { if ( m_users == null) m_users = new Hashtable(); - m_users.put(userName, new Integer(role)); + m_users.put(userName, Integer.valueOf(role)); } /** diff --git a/source/java/org/alfresco/filesys/repo/CIFSContentComparator.java b/source/java/org/alfresco/filesys/repo/CIFSContentComparator.java index 62415dfe4f..e85d169ff5 100644 --- a/source/java/org/alfresco/filesys/repo/CIFSContentComparator.java +++ b/source/java/org/alfresco/filesys/repo/CIFSContentComparator.java @@ -46,6 +46,7 @@ public class CIFSContentComparator implements ContentComparator public void init() { customComparators.put("application/vnd.ms-project", new MPPContentComparator()); + customComparators.put("application/vnd.ms-excel", new XLSContentComparator()); } @Override @@ -201,4 +202,83 @@ public class CIFSContentComparator implements ContentComparator } } } + + // Comparator for MS Excel + private class XLSContentComparator implements ContentComparator + { + + @Override + public boolean isContentEqual(ContentReader existingContent, + File newFile) + { + long newSize = newFile.length(); + + if(logger.isDebugEnabled()) + { + logger.debug("comparing two excel files size:" + existingContent.getSize() + ", and " + newFile.length()); + } + + if(existingContent.getSize() != newSize) + { + logger.debug("excel files are different size"); + // Different size + return false; + } + + /** + * Use POI to compare the content of the XLS file, exluding certain properties + */ + InputStream leftIs = null; + try + { + Collection excludes = new HashSet(); + + leftIs = existingContent.getContentInputStream(); + NPOIFSFileSystem fs2 = new NPOIFSFileSystem(leftIs); + NPOIFSFileSystem fs1 = new NPOIFSFileSystem(newFile); + + DirectoryEntry de1 = fs1.getRoot(); + DirectoryEntry de2 = fs2.getRoot(); + + FilteringDirectoryNode fs1Filtered = new FilteringDirectoryNode(de1, excludes); + FilteringDirectoryNode fs2Filtered = new FilteringDirectoryNode(de2, excludes); + + boolean retVal = EntryUtils.areDirectoriesIdentical(fs1Filtered, fs2Filtered); + if(logger.isDebugEnabled()) + { + logger.debug("returning equal="+ retVal); + } + + return retVal; + } + catch (ContentIOException ce) + { + logger.debug("Unable to compare contents", ce); + return false; + } + catch (IOException e) + { + logger.debug("Unable to compare contents", e); + return false; + } + finally + { + if(leftIs != null) + { + try + { + leftIs.close(); + } + catch (IOException e) + { + // Ignore + } + } + } + } + } + + + + } diff --git a/source/java/org/alfresco/filesys/repo/CIFSContentComparatorTest.java b/source/java/org/alfresco/filesys/repo/CIFSContentComparatorTest.java index 04ee6c9658..41449e1f9c 100644 --- a/source/java/org/alfresco/filesys/repo/CIFSContentComparatorTest.java +++ b/source/java/org/alfresco/filesys/repo/CIFSContentComparatorTest.java @@ -199,7 +199,53 @@ public class CIFSContentComparatorTest extends TestCase assertTrue("compare trivially different project file, should be equal", result); } } + + /** + * Open and close of an excel 2003 file changes certain header properties. + * Test File 1 has been opened and closed in excel2003. + * @throws Exception + */ + public void testDiffExcel2003Files() throws Exception + { + CIFSContentComparator contentComparator = new CIFSContentComparator(); + contentComparator.init(); + + ClassPathResource file0Resource = new ClassPathResource("filesys/ContentComparatorTestExcel2003-1.xls"); + assertNotNull("unable to find test resource filesys/filesys/ContentComparatorTestExcel2003-1.xls", file0Resource); + + ClassPathResource file1Resource = new ClassPathResource("filesys/ContentComparatorTestExcel2003-2.xls"); + assertNotNull("unable to find test resource filesys/filesys/ContentComparatorTestExcel2003-2.xls", file1Resource); + + ClassPathResource file3Resource = new ClassPathResource("filesys/ContentComparatorTestExcel2003-3.xls"); + assertNotNull("unable to find test resource filesys/filesys/ContentComparatorTestExcel2003-3.xls", file1Resource); + + /** + * Compare trivially different excel files, should ignore trivial differences and be equal + */ + { + File file0 = file0Resource.getFile(); + File file1 = file1Resource.getFile(); + ContentReader reader = new FileContentReader(file0); + reader.setMimetype("application/vnd.ms-excel"); + reader.setEncoding("UTF-8"); + boolean result = contentComparator.isContentEqual(reader, file1); + assertTrue("compare trivially different project file, should be equal", result); + } + + /** + * Compare different project files, should not be ignored + */ + { + File file0 = file0Resource.getFile(); + File file3 = file3Resource.getFile(); + ContentReader reader = new FileContentReader(file0); + reader.setMimetype("application/vnd.ms-excel"); + reader.setEncoding("UTF-8"); + boolean result = contentComparator.isContentEqual(reader, file3); + assertTrue("different excel2003 file, failed to note difference", !result); + } + } } diff --git a/source/java/org/alfresco/filesys/repo/ContentContext.java b/source/java/org/alfresco/filesys/repo/ContentContext.java index 66b4f28b0c..6c21052a16 100644 --- a/source/java/org/alfresco/filesys/repo/ContentContext.java +++ b/source/java/org/alfresco/filesys/repo/ContentContext.java @@ -85,7 +85,8 @@ public class ContentContext extends AlfrescoContext private ThreadRequestPool m_threadPool; - private Pattern renameShufflePattern = Pattern.compile("(.*\\.tmp)|(.*\\.wbk)|(.*\\.bak)|(.*\\~)"); + // pattern is tested against full path after it has been lower cased. + private Pattern renameShufflePattern = Pattern.compile("(.*[a-f0-9]{8}+$)|(.*\\.tmp$)|(.*\\.wbk$)|(.*\\.bak$)|(.*\\~$)"); /** * Default constructor allowing initialization by container. diff --git a/source/java/org/alfresco/filesys/repo/ContentDiskDriver2.java b/source/java/org/alfresco/filesys/repo/ContentDiskDriver2.java index 90dda19a76..bb0d74e72e 100644 --- a/source/java/org/alfresco/filesys/repo/ContentDiskDriver2.java +++ b/source/java/org/alfresco/filesys/repo/ContentDiskDriver2.java @@ -2255,6 +2255,7 @@ public class ContentDiskDriver2 extends AlfrescoDiskDriver implements ExtendedD File file = TempFileProvider.createTempFile("cifs", ".bin"); TempNetworkFile netFile = new TempNetworkFile(file, path); + netFile.setChanged(true); // Always allow write access to a newly created file netFile.setGrantedAccess(NetworkFile.READWRITE); @@ -2684,7 +2685,7 @@ public class ContentDiskDriver2 extends AlfrescoDiskDriver implements ExtendedD { if(logger.isDebugEnabled()) { - logger.debug("Got a temp network file "); + logger.debug("Got a temp network file to close"); } // Some content was written to the temp file. @@ -2701,7 +2702,7 @@ public class ContentDiskDriver2 extends AlfrescoDiskDriver implements ExtendedD nodeService.removeAspect(target, ContentModel.ASPECT_NO_CONTENT); } - if(tempFile.getWriteCount() > 0) + if(tempFile.isChanged()) { tempFile.flushFile(); tempFile.close(); @@ -2721,7 +2722,7 @@ public class ContentDiskDriver2 extends AlfrescoDiskDriver implements ExtendedD if(contentChanged) { - logger.debug("content has changed"); + logger.debug("content has changed, need to create a new content item"); /** * Take over the behaviour of the auditable aspect diff --git a/source/java/org/alfresco/filesys/repo/ContentDiskDriverTest.java b/source/java/org/alfresco/filesys/repo/ContentDiskDriverTest.java index 67ac8a1870..92fd2039c7 100644 --- a/source/java/org/alfresco/filesys/repo/ContentDiskDriverTest.java +++ b/source/java/org/alfresco/filesys/repo/ContentDiskDriverTest.java @@ -2688,6 +2688,286 @@ public class ContentDiskDriverTest extends TestCase } } } + + /** + * Excel 2003 With Versionable file + * + * CreateFile 5EE27100 + * RenameFile oldPath:\Espaces Utilisateurs\System\Cherries.xls, + * newPath:\Espaces Utilisateurs\System\Cherries.xls~RF172f241.TMP + * RenameFile oldName=\Espaces Utilisateurs\System\5EE27100, + * newName=\Espaces Utilisateurs\System\Cherries.xls, session:WNB0 + * + * Set Delete On Close for Cherries.xls~RF172f241.TMP + */ + public void testExcel2003SaveShuffle() throws Exception + { + //fail("not yet implemented"); + logger.debug("testScenarioExcel2003SaveShuffle"); + final String FILE_NAME = "Cherries.xls"; + final String FILE_TITLE = "Cherries"; + final String FILE_DESCRIPTION = "This is a test document to test CIFS shuffle"; + final String FILE_OLD_TEMP = "Cherries.xls~RF172f241.TMP"; + final String FILE_NEW_TEMP = "5EE27100"; + + final QName RESIDUAL_MTTEXT = QName.createQName("{gsxhjsx}", "whatever"); + + class TestContext + { + NetworkFile firstFileHandle; + NetworkFile newFileHandle; + NetworkFile oldFileHandle; + + NodeRef testNodeRef; // node ref of test.doc + + Serializable testCreatedDate; + }; + + final TestContext testContext = new TestContext(); + + final String TEST_DIR = TEST_ROOT_DOS_PATH + "\\testScenarioMSExcel2003SaveShuffle"; + + ServerConfiguration scfg = new ServerConfiguration("testServer"); + TestServer testServer = new TestServer("testServer", scfg); + final SrvSession testSession = new TestSrvSession(666, testServer, "test", "remoteName"); + DiskSharedDevice share = getDiskSharedDevice(); + final TreeConnection testConnection = testServer.getTreeConnection(share); + final RetryingTransactionHelper tran = transactionService.getRetryingTransactionHelper(); + + /** + * Clean up just in case garbage is left from a previous run + */ + RetryingTransactionCallback deleteGarbageFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + driver.deleteFile(testSession, testConnection, TEST_DIR + "\\" + FILE_NAME); + return null; + } + }; + + /** + * Create a file in the test directory + */ + + try + { + tran.doInTransaction(deleteGarbageFileCB); + } + catch (Exception e) + { + // expect to go here + } + + RetryingTransactionCallback createFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + + /** + * Create the test directory we are going to use + */ + FileOpenParams createRootDirParams = new FileOpenParams(TEST_ROOT_DOS_PATH, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + FileOpenParams createDirParams = new FileOpenParams(TEST_DIR, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + driver.createDirectory(testSession, testConnection, createRootDirParams); + driver.createDirectory(testSession, testConnection, createDirParams); + + /** + * Create the file we are going to use + */ + FileOpenParams createFileParams = new FileOpenParams(TEST_DIR + "\\" + FILE_NAME, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + testContext.firstFileHandle = driver.createFile(testSession, testConnection, createFileParams); + assertNotNull(testContext.firstFileHandle); + + // now load up the node with lots of other stuff that we will test to see if it gets preserved during the + // shuffle. + testContext.testNodeRef = getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); + + nodeService.addAspect(testContext.testNodeRef, ContentModel.ASPECT_VERSIONABLE, null); + + // test non CM namespace property + nodeService.setProperty(testContext.testNodeRef, TransferModel.PROP_ENABLED, true); + // test CM property not related to an aspect + nodeService.setProperty(testContext.testNodeRef, ContentModel.PROP_ADDRESSEE, "Fred"); + + nodeService.setProperty(testContext.testNodeRef, ContentModel.PROP_TITLE, FILE_TITLE); + nodeService.setProperty(testContext.testNodeRef, ContentModel.PROP_DESCRIPTION, FILE_DESCRIPTION); + + /** + * MLText value - also a residual value in a non cm namespace + */ + MLText mltext = new MLText(); + mltext.addValue(Locale.FRENCH, "Bonjour"); + mltext.addValue(Locale.ENGLISH, "Hello"); + mltext.addValue(Locale.ITALY, "Buongiorno"); + mlAwareNodeService.setProperty(testContext.testNodeRef, RESIDUAL_MTTEXT, mltext); + + // classifiable chosen since its not related to any properties. + nodeService.addAspect(testContext.testNodeRef, ContentModel.ASPECT_CLASSIFIABLE, null); + //nodeService.createAssociation(testContext.testNodeRef, targetRef, assocTypeQName); + + return null; + } + }; + tran.doInTransaction(createFileCB, false, true); + + /** + * Write some content to the test file + */ + RetryingTransactionCallback writeFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + String testContent = "MS Excel 2003 shuffle test"; + byte[] testContentBytes = testContent.getBytes(); + testContext.firstFileHandle.writeFile(testContentBytes, testContentBytes.length, 0, 0); + testContext.firstFileHandle.close(); + + testContext.testCreatedDate = nodeService.getProperty(testContext.testNodeRef, ContentModel.PROP_CREATED); + + MLText multi = (MLText)mlAwareNodeService.getProperty(testContext.testNodeRef, RESIDUAL_MTTEXT) ; + multi.getValues(); + + + return null; + } + }; + tran.doInTransaction(writeFileCB, false, true); + + /** + * b) Save the new file + */ + RetryingTransactionCallback saveNewFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + FileOpenParams createFileParams = new FileOpenParams(TEST_DIR + "\\" + FILE_NEW_TEMP, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + testContext.newFileHandle = driver.createFile(testSession, testConnection, createFileParams); + assertNotNull(testContext.newFileHandle); + String testContent = "MS Word 2003 shuffle test This is new content"; + byte[] testContentBytes = testContent.getBytes(); + testContext.newFileHandle.writeFile(testContentBytes, testContentBytes.length, 0, 0); + testContext.newFileHandle.close(); + + return null; + } + }; + tran.doInTransaction(saveNewFileCB, false, true); + + /** + * rename the old file + */ + RetryingTransactionCallback renameOldFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + driver.renameFile(testSession, testConnection, TEST_DIR + "\\" + FILE_NAME, TEST_DIR + "\\" + FILE_OLD_TEMP); + return null; + } + }; + tran.doInTransaction(renameOldFileCB, false, true); + + + RetryingTransactionCallback validateOldFileGoneCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + try + { + driver.deleteFile(testSession, testConnection, TEST_DIR + "\\" + FILE_NAME); + } + catch (IOException e) + { + // expect to go here since previous step renamed the file. + } + + return null; + } + }; + tran.doInTransaction(validateOldFileGoneCB, false, true); + + /** + * Move the new file into place, stuff should get shuffled + */ + RetryingTransactionCallback moveNewFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + driver.renameFile(testSession, testConnection, TEST_DIR + "\\" + FILE_NEW_TEMP, TEST_DIR + "\\" + FILE_NAME); + return null; + } + }; + + tran.doInTransaction(moveNewFileCB, false, true); + + RetryingTransactionCallback validateCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + NodeRef shuffledNodeRef = getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); + + Map props = nodeService.getProperties(shuffledNodeRef); + + // Check trx:enabled has been shuffled. + assertTrue("node does not contain shuffled ENABLED property", props.containsKey(TransferModel.PROP_ENABLED)); + // check my residual MLText has been transferred + assertTrue(props.containsKey(RESIDUAL_MTTEXT)); + + // Check the titled aspect is correct + assertEquals("name wrong", FILE_NAME, nodeService.getProperty(shuffledNodeRef, ContentModel.PROP_NAME) ); + assertEquals("title wrong", FILE_TITLE, nodeService.getProperty(shuffledNodeRef, ContentModel.PROP_TITLE) ); + assertEquals("description wrong", FILE_DESCRIPTION, nodeService.getProperty(shuffledNodeRef, ContentModel.PROP_DESCRIPTION) ); + + // commented out due to ALF-7641 + // CIFS shuffle, does not preseve MLText values. + // Map mlProps = mlAwareNodeService.getProperties(shuffledNodeRef); + + // MLText multi = (MLText)mlAwareNodeService.getProperty(shuffledNodeRef, RESIDUAL_MTTEXT) ; + // multi.getValues(); + + // check auditable properties + // commented out due to ALF-7635 + // assertEquals("creation date not preserved", ((Date)testContext.testCreatedDate).getTime(), ((Date)nodeService.getProperty(shuffledNodeRef, ContentModel.PROP_CREATED)).getTime()); + + // commented out due to ALF-7628 + // assertEquals("ADDRESSEE PROPERTY Not copied", "Fred", nodeService.getProperty(shuffledNodeRef, ContentModel.PROP_ADDRESSEE)); + // assertTrue("CLASSIFIABLE aspect not present", nodeService.hasAspect(shuffledNodeRef, ContentModel.ASPECT_CLASSIFIABLE)); + + // commented out due to ALF-7584. + // assertEquals("noderef changed", testContext.testNodeRef, shuffledNodeRef); + return null; + } + }; + + tran.doInTransaction(validateCB, true, true); + + /** + * Clean up just in case garbage is left from a previous run + */ + RetryingTransactionCallback deleteOldFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + driver.deleteFile(testSession, testConnection, TEST_DIR + "\\" + FILE_OLD_TEMP); + return null; + } + }; + + tran.doInTransaction(deleteOldFileCB, false, true); + + } + + + /** * Simulates a SaveAs from Word2003 @@ -3348,8 +3628,9 @@ public class ContentDiskDriverTest extends TestCase } // Scenario frame maker save /** - * - * @throws Exception + * Test that rules fire on zero byte long files. + * In this case check that a new file gets the versionable + * aspect added. */ public void testZeroByteRules() throws Exception { @@ -3536,10 +3817,192 @@ public class ContentDiskDriverTest extends TestCase return null; } }; - tran.doInTransaction(validateFirstExtractionCB, false, true); - + tran.doInTransaction(validateFirstExtractionCB, false, true); } // testZeroByteRules + /** + * Test that files can be created with empty content and that + * existing content can be over-wrriten by empty content. + */ + public void testEmptyContent() throws Exception + { + logger.debug("testEmptyContent"); + final String FILE_NAME_ZERO = "Zero.docx"; + final String FILE_NAME_NON_ZERO = "NonZero.docx"; + + class TestContext + { + NodeRef testDirNodeRef; + NodeRef testZeroNodeRef; + NodeRef testNonZeroNodeRef; + NetworkFile firstFileHandle; + NetworkFile secondFileHandle; + }; + + final TestContext testContext = new TestContext(); + + final String TEST_DIR = TEST_ROOT_DOS_PATH + "\\testEmptyContent"; + + ServerConfiguration scfg = new ServerConfiguration("testServer"); + TestServer testServer = new TestServer("testServer", scfg); + final SrvSession testSession = new TestSrvSession(666, testServer, "test", "remoteName"); + DiskSharedDevice share = getDiskSharedDevice(); + final TreeConnection testConnection = testServer.getTreeConnection(share); + final RetryingTransactionHelper tran = transactionService.getRetryingTransactionHelper(); + + /** + * Clean up just in case garbage is left from a previous run + */ + RetryingTransactionCallback deleteGarbageDirCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + driver.deleteDirectory(testSession, testConnection, TEST_DIR); + return null; + } + }; + + try + { + tran.doInTransaction(deleteGarbageDirCB); + } + catch (Exception e) + { + // expect to go here + } + + logger.debug("create Test directory" + TEST_DIR); + RetryingTransactionCallback createTestDirCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + /** + * Create the test directory we are going to use + */ + FileOpenParams createRootDirParams = new FileOpenParams(TEST_ROOT_DOS_PATH, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + FileOpenParams createDirParams = new FileOpenParams(TEST_DIR, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + driver.createDirectory(testSession, testConnection, createRootDirParams); + driver.createDirectory(testSession, testConnection, createDirParams); + + testContext.testDirNodeRef = getNodeForPath(testConnection, TEST_DIR); + assertNotNull("testDirNodeRef is null", testContext.testDirNodeRef); + return null; + + + } + }; + tran.doInTransaction(createTestDirCB); + + /** + * Create a file in the test directory + */ + logger.debug("create test file in test directory"); + RetryingTransactionCallback createFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + /** + * Create the zero byte file we are going to use to test + */ + FileOpenParams createFileParams = new FileOpenParams(TEST_DIR + "\\" + FILE_NAME_ZERO, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + testContext.firstFileHandle = driver.createFile(testSession, testConnection, createFileParams); + assertNotNull(testContext.firstFileHandle); + + testContext.testZeroNodeRef = getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME_ZERO); + assertNotNull("testContext.testNodeRef is null", testContext.testZeroNodeRef); + + logger.debug("close the file, firstFileHandle"); + driver.closeFile(testSession, testConnection, testContext.firstFileHandle); + + /** + * Create the non zero byte file we are going to use to test + */ + FileOpenParams createFileParams2 = new FileOpenParams(TEST_DIR + "\\" + FILE_NAME_NON_ZERO, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + testContext.secondFileHandle = driver.createFile(testSession, testConnection, createFileParams2); + assertNotNull(testContext.secondFileHandle); + + testContext.testNonZeroNodeRef = getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME_NON_ZERO); + assertNotNull("testContext.testNodeRef is null", testContext.testNonZeroNodeRef); + + // Write hello world into the second file + byte[] stuff = "Hello World".getBytes(); + driver.writeFile(testSession, testConnection, testContext.secondFileHandle, stuff, 0, stuff.length, 0); + + logger.debug("close the second non zero file, secondFileHandle"); + driver.closeFile(testSession, testConnection, testContext.secondFileHandle); + + return null; + } + }; + tran.doInTransaction(createFileCB, false, true); + + /** + * d: check both files have content properties. + */ + RetryingTransactionCallback checkContentPropsCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + assertNotNull("content missing create non zero file.", nodeService.getProperty(testContext.testNonZeroNodeRef, ContentModel.PROP_CONTENT)); + assertNotNull("content missing create zero byte file.", nodeService.getProperty(testContext.testZeroNodeRef, ContentModel.PROP_CONTENT)); + return null; + } + }; + tran.doInTransaction(checkContentPropsCB, false, true); + + RetryingTransactionCallback truncateFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + /** + * Truncate the non zero byte file we are going to use to test + */ + FileOpenParams createFileParams2 = new FileOpenParams(TEST_DIR + "\\" + FILE_NAME_NON_ZERO, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + testContext.secondFileHandle = driver.openFile(testSession, testConnection, createFileParams2); + assertNotNull(testContext.secondFileHandle); + + testContext.testNonZeroNodeRef = getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME_NON_ZERO); + assertNotNull("testContext.testNodeRef is null", testContext.testNonZeroNodeRef); + + driver.truncateFile(testSession, testConnection, testContext.secondFileHandle, 0); + + logger.debug("close the second non zero file, secondFileHandle"); + driver.closeFile(testSession, testConnection, testContext.secondFileHandle); + + return null; + } + }; + tran.doInTransaction(truncateFileCB, false, true); + + /** + * d: check both files have content properties. + */ + RetryingTransactionCallback checkContentProps2CB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + ContentReader reader = contentService.getReader(testContext.testNonZeroNodeRef, ContentModel.PROP_CONTENT); + String s = reader.getContentString(); + assertEquals("content not truncated", "", s); + + ContentReader reader2 = contentService.getReader(testContext.testZeroNodeRef, ContentModel.PROP_CONTENT); + String s2 = reader2.getContentString(); + assertEquals("content not empty", "", s2); + return null; + } + }; + tran.doInTransaction(checkContentProps2CB, false, true); + + + + } // testEmptyFiles + /** * Simulates a SaveAs from Word2003 for a checked out file diff --git a/source/java/org/alfresco/filesys/repo/DeleteNodeEvent.java b/source/java/org/alfresco/filesys/repo/DeleteNodeEvent.java index 1e8b439453..416a219f79 100644 --- a/source/java/org/alfresco/filesys/repo/DeleteNodeEvent.java +++ b/source/java/org/alfresco/filesys/repo/DeleteNodeEvent.java @@ -33,8 +33,6 @@ public class DeleteNodeEvent extends NodeEvent { private String m_path; - private boolean m_deleteConfirm; - /** * Class constructor * @@ -57,24 +55,6 @@ public class DeleteNodeEvent extends NodeEvent { return m_path; } - /** - * Check if the delete confirm flag is set - * - * @return boolean - */ - public final boolean hasDeleteConfirm() { - return m_deleteConfirm; - } - - /** - * Set/clear the delete confirm flag - * - * @param delConfirm boolean - */ - public final void setDeleteConfirm( boolean delConfirm) { - m_deleteConfirm = delConfirm; - } - /** * Return the node event as a string * @@ -89,8 +69,6 @@ public class DeleteNodeEvent extends NodeEvent { str.append(getNodeRef()); str.append(",path="); str.append(getPath()); - str.append(",confirm="); - str.append(hasDeleteConfirm()); str.append("]"); return str.toString(); diff --git a/source/java/org/alfresco/filesys/repo/NodeMonitor.java b/source/java/org/alfresco/filesys/repo/NodeMonitor.java index 9ab87ff6d3..4ce21f40a2 100644 --- a/source/java/org/alfresco/filesys/repo/NodeMonitor.java +++ b/source/java/org/alfresco/filesys/repo/NodeMonitor.java @@ -682,15 +682,15 @@ public class NodeMonitor extends TransactionListenerAdapter // Check if the delete was confirmed - if ( deleteEvent.hasDeleteConfirm() == false) { - - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug("DeleteNode not confirmed, nodeRef=" + deleteEvent.getNodeRef() + ", path=" + deleteEvent.getPath()); - - return; - } +// if ( deleteEvent.hasDeleteConfirm() == false) { +// +// // DEBUG +// +// if ( logger.isDebugEnabled()) +// logger.debug("DeleteNode not confirmed, nodeRef=" + deleteEvent.getNodeRef() + ", path=" + deleteEvent.getPath()); +// +// return; +// } // Strip the root path diff --git a/source/java/org/alfresco/filesys/repo/TempNetworkFile.java b/source/java/org/alfresco/filesys/repo/TempNetworkFile.java index 33af1b3bac..135a9a4e71 100644 --- a/source/java/org/alfresco/filesys/repo/TempNetworkFile.java +++ b/source/java/org/alfresco/filesys/repo/TempNetworkFile.java @@ -15,6 +15,8 @@ import org.alfresco.jlan.smb.server.disk.JavaNetworkFile; */ public class TempNetworkFile extends JavaNetworkFile implements NetworkFileStateInterface { + private boolean changed = false; + /** * Create a new temporary file with no existing content. * @@ -67,6 +69,7 @@ public class TempNetworkFile extends JavaNetworkFile implements NetworkFileState @Override public void writeFile(byte[] buf, int len, int pos) throws IOException { + changed = true; super.writeFile(buf, len, pos); @@ -85,6 +88,7 @@ public class TempNetworkFile extends JavaNetworkFile implements NetworkFileState public void writeFile(byte[] buffer, int length, int position, long fileOffset) throws IOException { + changed = true; super.writeFile(buffer, length, position, fileOffset); @@ -103,6 +107,11 @@ public class TempNetworkFile extends JavaNetworkFile implements NetworkFileState { super.truncateFile(size); + if(size == 0) + { + changed = true; + } + setFileSize(size); if(fileState != null) { @@ -133,5 +142,16 @@ public class TempNetworkFile extends JavaNetworkFile implements NetworkFileState } + public void setChanged(boolean changed) + { + this.changed = changed; + } + + public boolean isChanged() + { + return changed; + } + + private FileState fileState; } diff --git a/source/java/org/alfresco/filesys/repo/rules/commands/ReduceQuotaCommand.java b/source/java/org/alfresco/filesys/repo/rules/commands/ReduceQuotaCommand.java index f7a7432497..a37e8bfe59 100644 --- a/source/java/org/alfresco/filesys/repo/rules/commands/ReduceQuotaCommand.java +++ b/source/java/org/alfresco/filesys/repo/rules/commands/ReduceQuotaCommand.java @@ -1,68 +1,70 @@ -/* - * 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.filesys.repo.rules.commands; - -import org.alfresco.filesys.repo.rules.Command; -import org.alfresco.jlan.server.filesys.NetworkFile; -import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; -import org.alfresco.service.cmr.repository.NodeRef; - -/** - * Open File command - */ -public class ReduceQuotaCommand implements Command -{ - private String name; - private String path; - private NodeRef rootNode; - - private NetworkFile networkFile; - - public ReduceQuotaCommand(String name, NetworkFile networkFile, NodeRef rootNode, String path) - { - this.name = name; - this.networkFile = networkFile; - } - - public String getName() - { - return name; - } - - public NetworkFile getNetworkFile() - { - return networkFile; - } - - public NodeRef getRootNodeRef() - { - return rootNode; - } - - public String getPath() - { - return path; - } - - @Override - public TxnReadState getTransactionRequired() - { - return TxnReadState.TXN_NONE; - } -} +/* + * 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.filesys.repo.rules.commands; + +import org.alfresco.filesys.repo.rules.Command; +import org.alfresco.jlan.server.filesys.NetworkFile; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Open File command + */ +public class ReduceQuotaCommand implements Command +{ + private String name; + private String path; + private NodeRef rootNode; + + private NetworkFile networkFile; + + public ReduceQuotaCommand(String name, NetworkFile networkFile, NodeRef rootNode, String path) + { + this.name = name; + this.networkFile = networkFile; + this.rootNode = rootNode; + this.path = path; + } + + public String getName() + { + return name; + } + + public NetworkFile getNetworkFile() + { + return networkFile; + } + + public NodeRef getRootNodeRef() + { + return rootNode; + } + + public String getPath() + { + return path; + } + + @Override + public TxnReadState getTransactionRequired() + { + return TxnReadState.TXN_NONE; + } +} diff --git a/source/java/org/alfresco/filesys/util/CifsMounter.java b/source/java/org/alfresco/filesys/util/CifsMounter.java index a3df98cee4..7da8126aca 100644 --- a/source/java/org/alfresco/filesys/util/CifsMounter.java +++ b/source/java/org/alfresco/filesys/util/CifsMounter.java @@ -20,6 +20,7 @@ */ package org.alfresco.filesys.util; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; @@ -207,7 +208,7 @@ public class CifsMounter { // Get the command to be used on this platform if ( logger.isDebugEnabled()) - logger.debug( "Mount CIFS share, cmdLine=" + exec.getCommand()); + logger.debug( "Mount CIFS share, cmdLine=" + Arrays.toString(exec.getCommand())); // Run the command @@ -280,7 +281,7 @@ public class CifsMounter { // Get the command to be used on this platform if ( logger.isDebugEnabled()) - logger.debug( "UnMount CIFS share, cmdLine=" + exec.getCommand()); + logger.debug( "UnMount CIFS share, cmdLine=" + Arrays.toString(exec.getCommand())); // Run the command diff --git a/source/java/org/alfresco/jcr/dictionary/NodeTypeImpl.java b/source/java/org/alfresco/jcr/dictionary/NodeTypeImpl.java index 5be13d12a8..811a620708 100644 --- a/source/java/org/alfresco/jcr/dictionary/NodeTypeImpl.java +++ b/source/java/org/alfresco/jcr/dictionary/NodeTypeImpl.java @@ -205,7 +205,7 @@ public class NodeTypeImpl implements NodeType } } - if (classDefinition.equals(NT_BASE)) + if (classDefinition.getName().equals(NT_BASE)) { // add nt:base properties defs.add(typeManager.getPropertyDefinitionImpl(JCRPrimaryTypeProperty.PROPERTY_NAME)); diff --git a/source/java/org/alfresco/model/ImapModel.java b/source/java/org/alfresco/model/ImapModel.java index 63da60ed04..c967ce0d80 100644 --- a/source/java/org/alfresco/model/ImapModel.java +++ b/source/java/org/alfresco/model/ImapModel.java @@ -60,4 +60,8 @@ public interface ImapModel static final QName PROP_MAXUID = QName.createQName(IMAP_MODEL_1_0_URI, "maxUid"); static final QName PROP_CHANGE_TOKEN = QName.createQName(IMAP_MODEL_1_0_URI, "changeToken"); + + static final QName ASPECT_IMAP_PREFERENCES = QName.createQName(IMAP_MODEL_1_0_URI, "imapPreferences"); + static final QName ASSOC_IMAP_UNSUBSCRIBED = QName.createQName(IMAP_MODEL_1_0_URI, "imapUnsubscribed"); + } diff --git a/source/java/org/alfresco/opencmis/AlfrescoCmisServiceImpl.java b/source/java/org/alfresco/opencmis/AlfrescoCmisServiceImpl.java index b54cbe1d15..633c6a6ad5 100644 --- a/source/java/org/alfresco/opencmis/AlfrescoCmisServiceImpl.java +++ b/source/java/org/alfresco/opencmis/AlfrescoCmisServiceImpl.java @@ -1072,6 +1072,19 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr return nodeRef.toString(); } + private String stripEncoding(String mimeType) + { + String ret = mimeType; + + int idx = mimeType.indexOf(";"); + if(idx != -1) + { + ret = mimeType.substring(0, idx); + } + + return ret; + } + @Override public String createDocument( String repositoryId, final Properties properties, String folderId, @@ -1127,7 +1140,7 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr { // write content ContentWriter writer = connector.getFileFolderService().getWriter(nodeRef); - writer.setMimetype(contentStream.getMimeType()); + writer.setMimetype(stripEncoding(contentStream.getMimeType())); writer.setEncoding(encoding.name()); writer.putContent(tempFile); } diff --git a/source/java/org/alfresco/opencmis/AlfrescoLocalCmisServiceFactory.java b/source/java/org/alfresco/opencmis/AlfrescoLocalCmisServiceFactory.java index 65cfff63b0..74ee4ba6ed 100644 --- a/source/java/org/alfresco/opencmis/AlfrescoLocalCmisServiceFactory.java +++ b/source/java/org/alfresco/opencmis/AlfrescoLocalCmisServiceFactory.java @@ -61,11 +61,13 @@ public class AlfrescoLocalCmisServiceFactory extends AbstractServiceFactory CmisServiceWrapper wrapperService = THREAD_LOCAL_SERVICE.get(); if (wrapperService == null) { - wrapperService = new CmisServiceWrapper(new AlfrescoCmisServiceImpl(CMIS_CONNECTOR), + AlfrescoCmisService cmisService = new AlfrescoCmisServiceImpl(CMIS_CONNECTOR); + wrapperService = new CmisServiceWrapper(cmisService, CMIS_CONNECTOR.getTypesDefaultMaxItems(), CMIS_CONNECTOR.getTypesDefaultDepth(), CMIS_CONNECTOR.getObjectsDefaultMaxItems(), CMIS_CONNECTOR.getObjectsDefaultDepth()); THREAD_LOCAL_SERVICE.set(wrapperService); } + wrapperService.getWrappedService().open(context); return wrapperService; } } diff --git a/source/java/org/alfresco/opencmis/CMISConnector.java b/source/java/org/alfresco/opencmis/CMISConnector.java index 363de4e2ae..a9dc1f6cfe 100644 --- a/source/java/org/alfresco/opencmis/CMISConnector.java +++ b/source/java/org/alfresco/opencmis/CMISConnector.java @@ -1864,7 +1864,7 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen principalId = AuthenticationUtil.getFullyAuthenticatedUser(); } - for (String permission : translatePermmissionsFromCMIS(ace.getPermissions())) + for (String permission : translatePermissionsFromCMIS(ace.getPermissions())) { AccessPermission toCheck = new AccessPermissionImpl(permission, AccessStatus.ALLOWED, principalId, @@ -1890,7 +1890,7 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen principalId = AuthenticationUtil.getFullyAuthenticatedUser(); } - for (String permission : translatePermmissionsFromCMIS(ace.getPermissions())) + for (String permission : translatePermissionsFromCMIS(ace.getPermissions())) { permissionService.setPermission(nodeRef, principalId, permission, true); } @@ -1915,6 +1915,8 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen throw new CmisConstraintException("Object is not ACL controllable!"); } + Set currentAces = permissionService.getAllSetPermissions(nodeRef); + // remove all permissions permissionService.deletePermissions(nodeRef); @@ -1927,14 +1929,60 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen principalId = AuthenticationUtil.getFullyAuthenticatedUser(); } - for (String permission : translatePermmissionsFromCMIS(ace.getPermissions())) + List permissions = translatePermissionsFromCMIS(ace.getPermissions()); + normalisePermissions(currentAces, permissions); + for (String permission : permissions) { permissionService.setPermission(nodeRef, principalId, permission, true); } } } - private List translatePermmissionsFromCMIS(List permissions) + /* + * ALF-11868: the cmis client library may incorrectly send READ or WRITE permissions to applyAcl. + * This method works around this by "normalising" permissions: + * + *
    + *
  • the WRITE permission is removed from permissions if the cmis:write permission is being removed i.e. is in currentAccessPermissions but not in newPermissions + *
  • the cmis:write permission is removed from permissions if the WRITE permission is being removed i.e. is in currentAccessPermissions but not in newPermissions + *
  • the READ permission is removed from permissions if the cmis:read permission is being removed i.e. is in currentAccessPermissions but not in newPermissions + *
  • the cmis:read permission is removed from permissions if the READ permission is being removed i.e. is in currentAccessPermissions but not in newPermissions + *
+ */ + private void normalisePermissions(Set currentAccessPermissions, List newPermissions) + { + Set currentPermissions = new HashSet(currentAccessPermissions.size()); + for(AccessPermission accessPermission : currentAccessPermissions) + { + currentPermissions.add(accessPermission.getPermission()); + } + + if(currentPermissions.contains(PermissionService.WRITE) && !newPermissions.contains(BasicPermissions.WRITE) && newPermissions.contains(PermissionService.WRITE)) + { + // cmis:write is being removed, so remove WRITE from permissions + newPermissions.remove(PermissionService.WRITE); + } + + if(currentPermissions.contains(PermissionService.WRITE) && !newPermissions.contains(PermissionService.WRITE) && newPermissions.contains(BasicPermissions.WRITE)) + { + // WRITE is being removed, so remove cmis:write from permissions + newPermissions.remove(BasicPermissions.WRITE); + } + + if(currentPermissions.contains(PermissionService.READ) && !newPermissions.contains(BasicPermissions.READ) && newPermissions.contains(PermissionService.READ)) + { + // cmis:read is being removed, so remove READ from permissions + newPermissions.remove(PermissionService.READ); + } + + if(currentPermissions.contains(PermissionService.READ) && !newPermissions.contains(PermissionService.READ) && newPermissions.contains(BasicPermissions.READ)) + { + // READ is being removed, so remove cmis:read from permissions + newPermissions.remove(BasicPermissions.READ); + } + } + + private List translatePermissionsFromCMIS(List permissions) { List result = new ArrayList(); @@ -2081,7 +2129,11 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen result.getObjects().add(hit); } - result.setNumItems(null); + int length = rs.getLength(); + if(length != -1) + { + result.setNumItems(BigInteger.valueOf(length)); + } result.setHasMoreItems(rs.hasMore()); } finally diff --git a/source/java/org/alfresco/opencmis/CMISNodeInfoImpl.java b/source/java/org/alfresco/opencmis/CMISNodeInfoImpl.java index 166d68c612..f71c629185 100644 --- a/source/java/org/alfresco/opencmis/CMISNodeInfoImpl.java +++ b/source/java/org/alfresco/opencmis/CMISNodeInfoImpl.java @@ -34,7 +34,9 @@ import org.alfresco.opencmis.dictionary.FolderTypeDefintionWrapper; import org.alfresco.opencmis.dictionary.RelationshipTypeDefintionWrapper; import org.alfresco.opencmis.dictionary.TypeDefinitionWrapper; import org.alfresco.repo.security.permissions.AccessDeniedException; +import org.alfresco.repo.version.Version2Model; import org.alfresco.repo.version.VersionBaseModel; +import org.alfresco.repo.version.VersionModel; import org.alfresco.service.cmr.lock.LockType; import org.alfresco.service.cmr.repository.AssociationRef; import org.alfresco.service.cmr.repository.ChildAssociationRef; @@ -439,7 +441,10 @@ public class CMISNodeInfoImpl implements CMISNodeInfo { if (currentVersion.getVersionType() == VersionType.MAJOR) { - isLatestMajorVersion = currentVersion.getFrozenStateNodeRef().equals(nodeRef); + // ALF-11116: the current node (in the main store) and the frozen node (in the version store) are both represented as CMISNodeInfos + // but are indistinguishable apart from their storeRef (their objectVariant can be the same). + isLatestMajorVersion = (nodeRef.getStoreRef().getIdentifier().equals(Version2Model.STORE_ID) || nodeRef.getStoreRef().getIdentifier().equals(VersionModel.STORE_ID)) ? + currentVersion.getFrozenStateNodeRef().equals(nodeRef) : currentVersion.getVersionedNodeRef().equals(nodeRef); break; } currentVersion = versionHistory.getPredecessor(currentVersion); diff --git a/source/java/org/alfresco/repo/action/ActionServiceImpl.java b/source/java/org/alfresco/repo/action/ActionServiceImpl.java index f81252097e..ddad5a45d0 100644 --- a/source/java/org/alfresco/repo/action/ActionServiceImpl.java +++ b/source/java/org/alfresco/repo/action/ActionServiceImpl.java @@ -46,6 +46,7 @@ import org.alfresco.service.cmr.action.ActionDefinition; import org.alfresco.service.cmr.action.ActionList; import org.alfresco.service.cmr.action.ActionService; import org.alfresco.service.cmr.action.ActionServiceException; +import org.alfresco.service.cmr.action.ActionServiceTransientException; import org.alfresco.service.cmr.action.ActionStatus; import org.alfresco.service.cmr.action.ActionTrackingService; import org.alfresco.service.cmr.action.CompositeAction; @@ -723,6 +724,15 @@ public class ActionServiceImpl implements ActionService, RuntimeActionService, A } } } + catch (ActionServiceTransientException transientException) + { + // This is a non-fatal exception which will be recorded as a failed action, + // but which will not lead to the execution of any compensating action + if (getTrackStatus(action)) + { + actionTrackingService.recordActionFailure(action, transientException); + } + } catch (Throwable exception) { // DH: No logging of the exception. Leave the logging decision diff --git a/source/java/org/alfresco/repo/action/ActionServiceImplTest.java b/source/java/org/alfresco/repo/action/ActionServiceImplTest.java index 2944d52ab5..9ed0143d9e 100644 --- a/source/java/org/alfresco/repo/action/ActionServiceImplTest.java +++ b/source/java/org/alfresco/repo/action/ActionServiceImplTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -20,6 +20,8 @@ package org.alfresco.repo.action; import java.io.Serializable; import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -47,6 +49,7 @@ import org.alfresco.service.cmr.action.ActionCondition; import org.alfresco.service.cmr.action.ActionConditionDefinition; import org.alfresco.service.cmr.action.ActionDefinition; import org.alfresco.service.cmr.action.ActionService; +import org.alfresco.service.cmr.action.ActionServiceTransientException; import org.alfresco.service.cmr.action.ActionStatus; import org.alfresco.service.cmr.action.ActionTrackingService; import org.alfresco.service.cmr.action.CancellableAction; @@ -63,6 +66,7 @@ import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.ApplicationContextHelper; import org.alfresco.util.BaseAlfrescoSpringTest; import org.alfresco.util.GUID; import org.alfresco.util.PropertyMap; @@ -81,18 +85,18 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest private NodeRef folder; private RetryingTransactionHelper transactionHelper; -// @Override -// protected String[] getConfigLocations() -// { -// String[] existingConfigLocations = ApplicationContextHelper.CONFIG_LOCATIONS; -// -// List locations = Arrays.asList(existingConfigLocations); -// List mutableLocationsList = new ArrayList(locations); -// mutableLocationsList.add("classpath:org/alfresco/repo/action/test-action-services-context.xml"); -// -// String[] result = mutableLocationsList.toArray(new String[mutableLocationsList.size()]); -// return (String[]) result; -// } + @Override + protected String[] getConfigLocations() + { + String[] existingConfigLocations = ApplicationContextHelper.CONFIG_LOCATIONS; + + List locations = Arrays.asList(existingConfigLocations); + List mutableLocationsList = new ArrayList(locations); + mutableLocationsList.add("classpath:org/alfresco/repo/action/test-action-services-context.xml"); + + String[] result = mutableLocationsList.toArray(new String[mutableLocationsList.size()]); + return result; + } @Override protected void onSetUpInTransaction() throws Exception @@ -1043,7 +1047,7 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest public void testSyncFailureBehaviour() { // Create an action that is going to fail - Action action = createFailingMoveAction(); + Action action = createFailingMoveAction(true); try { @@ -1087,20 +1091,30 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest */ public void testCompensatingAction() { - // Create an action that is going to fail - final Action action = createFailingMoveAction(); - action.setTitle("title"); + // Create actions that are going to fail + final Action fatalAction = createFailingMoveAction(true); + final Action nonfatalAction = createFailingMoveAction(false); + fatalAction.setTitle("fatal title"); + nonfatalAction.setTitle("non-fatal title"); - // Create the compensating action + // Create the compensating actions Action compensatingAction = actionService.createAction(AddFeaturesActionExecuter.NAME); compensatingAction.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_CLASSIFIABLE); compensatingAction.setTitle("title"); - action.setCompensatingAction(compensatingAction); + fatalAction.setCompensatingAction(compensatingAction); - // Set the action to execute asynchronously - action.setExecuteAsynchronously(true); + Action compensatingAction2 = actionService.createAction(AddFeaturesActionExecuter.NAME); + compensatingAction2.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_TEMPORARY); + compensatingAction2.setTitle("title"); + nonfatalAction.setCompensatingAction(compensatingAction2); - this.actionService.executeAction(action, this.nodeRef); + + // Set the actions to execute asynchronously + fatalAction.setExecuteAsynchronously(true); + nonfatalAction.setExecuteAsynchronously(true); + + this.actionService.executeAction(fatalAction, this.nodeRef); + this.actionService.executeAction(nonfatalAction, this.nodeRef); setComplete(); endTransaction(); @@ -1113,8 +1127,21 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest { public String executeTest() { - boolean result = ActionServiceImplTest.this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_CLASSIFIABLE); - return result == true ? null : "Expected aspect Classifiable"; + boolean fatalCompensatingActionRun = ActionServiceImplTest.this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_CLASSIFIABLE); + + boolean nonFatalCompensatingActionRun = ActionServiceImplTest.this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_TEMPORARY); + + StringBuilder result = new StringBuilder(); + if (!fatalCompensatingActionRun) + { + result.append("Expected aspect Classifiable."); + } + if (nonFatalCompensatingActionRun) + { + result.append(" Did not expect aspect Temporary"); + } + + return ( !fatalCompensatingActionRun || nonFatalCompensatingActionRun ? result.toString() : null); }; }); @@ -1128,7 +1155,7 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest { try { - ActionServiceImplTest.this.actionService.executeAction(action, ActionServiceImplTest.this.nodeRef); + ActionServiceImplTest.this.actionService.executeAction(fatalAction, ActionServiceImplTest.this.nodeRef); } catch (RuntimeException exception) { @@ -1265,22 +1292,37 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest assertEquals(null, action.getExecutionFailureMessage()); } - protected Action createFailingMoveAction() { - Action failingAction = this.actionService.createAction(MoveActionExecuter.NAME); - - // Create a bad node ref - NodeRef badNodeRef = new NodeRef(this.storeRef, "123123"); - failingAction.setParameterValue(MoveActionExecuter.PARAM_DESTINATION_FOLDER, badNodeRef); - - return failingAction; + /** + * This method returns an {@link Action} which will fail when executed. + * + * @param isFatal if false this will give an action which throws + * a {@link ActionServiceTransientException non-fatal action exception}. + */ + protected Action createFailingMoveAction(boolean isFatal) { + Action failingAction; + if (isFatal) + { + failingAction = this.actionService.createAction(MoveActionExecuter.NAME); + + // Create a bad node ref + NodeRef badNodeRef = new NodeRef(this.storeRef, "123123"); + failingAction.setParameterValue(MoveActionExecuter.PARAM_DESTINATION_FOLDER, badNodeRef); + } + else + { + failingAction = this.actionService.createAction(TransientFailActionExecuter.NAME); + } + + return failingAction; } - protected Action createFailingSleepAction(String id) throws Exception { - return createFailingSleepAction(id, this.actionService); + protected Action createFailingSleepAction(String id, boolean isFatal) throws Exception { + return createFailingSleepAction(id, isFatal, this.actionService); } - protected static Action createFailingSleepAction(String id, ActionService actionService) throws Exception { + protected static Action createFailingSleepAction(String id, boolean isFatal, ActionService actionService) throws Exception { Action failingAction = createWorkingSleepAction(id, actionService); failingAction.setParameterValue(SleepActionExecuter.GO_BANG, Boolean.TRUE); + failingAction.setParameterValue(SleepActionExecuter.FAIL_FATALLY, Boolean.valueOf(isFatal)); return failingAction; } @@ -1328,6 +1370,7 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest { public static final String NAME = "sleep-action"; public static final String GO_BANG = "GoBang"; + public static final String FAIL_FATALLY = "failFatally"; private int sleepMs; private Thread executingThread; @@ -1397,10 +1440,21 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest incrementTimesExecutedCount(); } - Boolean fail = (Boolean)action.getParameterValue(GO_BANG); - if(fail != null && fail) + Boolean fail = (Boolean)action.getParameterValue(GO_BANG); + Boolean failFatally = (Boolean)action.getParameterValue(FAIL_FATALLY); + if (fail != null && fail) { - throw new RuntimeException("Bang!"); + // this should fail + if (failFatally != null && failFatally) + { + // this should fail fatally + throw new RuntimeException("Bang!"); + } + else + { + // this should fail non-fatally + throw new ActionServiceTransientException("Pop!"); + } } if(action instanceof CancellableSleepAction) @@ -1412,6 +1466,48 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest } } } + + /** + * This class is only intended for use in JUnit tests. + * + * @author Neil McErlean. + * @since 4.0.1 + */ + public static class TransientFailActionExecuter extends ActionExecuterAbstractBase + { + public static final String NAME = "transient-fail-action"; + + private ActionTrackingService actionTrackingService; + + /** + * Loads this executor into the ApplicationContext, if it isn't already there + */ + public static void registerIfNeeded(ConfigurableApplicationContext ctx) + { + if (!ctx.containsBean(TransientFailActionExecuter.NAME)) + { + // Create, and do dependencies + TransientFailActionExecuter executor = new TransientFailActionExecuter(); + executor.setTrackStatus(true); + executor.actionTrackingService = (ActionTrackingService) ctx.getBean("actionTrackingService"); + // Register + ctx.getBeanFactory().registerSingleton(TransientFailActionExecuter.NAME, executor); + } + } + + @Override protected void addParameterDefinitions(List paramList) + { + // Intentionally empty + } + + @Override protected void executeImpl(Action action, NodeRef actionedUponNodeRef) { + // this action always fails with a non-fatal exception. + throw new ActionServiceTransientException("action failed intentionally in " + TransientFailActionExecuter.class.getSimpleName()); + } + } + + + protected static class CancellableSleepAction extends ActionImpl implements CancellableAction { public CancellableSleepAction(Action action) diff --git a/source/java/org/alfresco/repo/action/ActionTrackingServiceImpl.java b/source/java/org/alfresco/repo/action/ActionTrackingServiceImpl.java index 4b619126d5..8c82509f14 100644 --- a/source/java/org/alfresco/repo/action/ActionTrackingServiceImpl.java +++ b/source/java/org/alfresco/repo/action/ActionTrackingServiceImpl.java @@ -33,6 +33,7 @@ import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.TransactionListenerAdapter; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ActionServiceTransientException; import org.alfresco.service.cmr.action.ActionStatus; import org.alfresco.service.cmr.action.ActionTrackingService; import org.alfresco.service.cmr.action.CancellableAction; @@ -317,6 +318,10 @@ public class ActionTrackingServiceImpl implements ActionTrackingService { logger.debug("Will shortly record completed cancellation of action " + action); } + else if (exception instanceof ActionServiceTransientException) + { + logger.debug("Will shortly record transient failure of action " + action); + } else { logger.debug("Will shortly record failure of action " + action + " due to " @@ -333,6 +338,11 @@ public class ActionTrackingServiceImpl implements ActionTrackingService ((ActionImpl) action).setExecutionStatus(ActionStatus.Cancelled); ((ActionImpl) action).setExecutionFailureMessage(null); } + else if (exception instanceof ActionServiceTransientException) + { + ((ActionImpl) action).setExecutionStatus(ActionStatus.Declined); + ((ActionImpl) action).setExecutionFailureMessage(exception.getMessage()); + } else { ((ActionImpl) action).setExecutionStatus(ActionStatus.Failed); diff --git a/source/java/org/alfresco/repo/action/ActionTrackingServiceImplTest.java b/source/java/org/alfresco/repo/action/ActionTrackingServiceImplTest.java index 6fed790623..109ee628c1 100644 --- a/source/java/org/alfresco/repo/action/ActionTrackingServiceImplTest.java +++ b/source/java/org/alfresco/repo/action/ActionTrackingServiceImplTest.java @@ -37,14 +37,15 @@ import org.alfresco.repo.action.executer.MoveActionExecuter; import org.alfresco.repo.cache.SimpleCache; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.jscript.ClasspathScriptLocation; +import org.alfresco.repo.policy.Behaviour.NotificationFrequency; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; -import org.alfresco.repo.policy.Behaviour.NotificationFrequency; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.ActionService; +import org.alfresco.service.cmr.action.ActionServiceTransientException; import org.alfresco.service.cmr.action.ActionStatus; import org.alfresco.service.cmr.action.ActionTrackingService; import org.alfresco.service.cmr.action.ExecutionDetails; @@ -389,66 +390,83 @@ public class ActionTrackingServiceImplTest extends TestCase } /** Failing actions go into the cache, then out */ - public void testFailingActions() throws Exception + public void testFatallyFailingActions() throws Exception { - final SleepActionExecuter sleepActionExec = - (SleepActionExecuter)ctx.getBean(SleepActionExecuter.NAME); - sleepActionExec.setSleepMs(10000); + Action failedAction = performFailingActionImpl(true, "54321"); + + assertEquals(ActionStatus.Failed, failedAction.getExecutionStatus()); + assertEquals("Bang!", failedAction.getExecutionFailureMessage()); + } + + /** Failing actions go into the cache, then out */ + public void testTransientlyFailingActions() throws Exception + { + Action failedAction = performFailingActionImpl(false, "654321"); + + assertEquals(ActionStatus.Declined, failedAction.getExecutionStatus()); + assertTrue(failedAction.getExecutionFailureMessage().endsWith("Pop!")); + } + + private Action performFailingActionImpl(boolean fatalFailure, String actionId) throws Exception + { + final SleepActionExecuter sleepActionExec = + (SleepActionExecuter)ctx.getBean(SleepActionExecuter.NAME); + sleepActionExec.setSleepMs(10000); - // Have it run asynchronously - UserTransaction txn = transactionService.getUserTransaction(); - txn.begin(); - Action action = createFailingSleepAction("54321"); - assertNull(action.getExecutionStartDate()); - assertNull(action.getExecutionEndDate()); - assertNull(action.getExecutionFailureMessage()); - assertEquals(ActionStatus.New, action.getExecutionStatus()); - - String key = ActionTrackingServiceImpl.generateCacheKey(action); - assertEquals(null, executingActionsCache.get(key)); - - this.actionService.executeAction(action, this.nodeRef, false, true); - - - // End the transaction. Should allow the async action - // to be started, and move into its sleeping phase - txn.commit(); - Thread.sleep(150); - - - // Will get an execution instance id, so a new key - key = ActionTrackingServiceImpl.generateCacheKey(action); - - - // Check it's in the cache - System.out.println("Checking the cache for " + key); - assertNotNull(executingActionsCache.get(key)); - - ExecutionSummary s = ActionTrackingServiceImpl.buildExecutionSummary(action); - ExecutionDetails d = actionTrackingService.getExecutionDetails(s); - assertNotNull(d.getExecutionSummary()); - assertEquals("sleep-action", d.getActionType()); - assertEquals("54321", d.getActionId()); - assertEquals(1, d.getExecutionInstance()); - assertEquals(null, d.getPersistedActionRef()); - assertNotNull(null, d.getStartedAt()); + // Have it run asynchronously + UserTransaction txn = transactionService.getUserTransaction(); + txn.begin(); + Action action = createFailingSleepAction(actionId, fatalFailure); + assertNull(action.getExecutionStartDate()); + assertNull(action.getExecutionEndDate()); + assertNull(action.getExecutionFailureMessage()); + assertEquals(ActionStatus.New, action.getExecutionStatus()); + + String key = ActionTrackingServiceImpl.generateCacheKey(action); + assertEquals(null, executingActionsCache.get(key)); + + this.actionService.executeAction(action, this.nodeRef, false, true); + + + // End the transaction. Should allow the async action + // to be started, and move into its sleeping phase + txn.commit(); + Thread.sleep(150); + + + // Will get an execution instance id, so a new key + key = ActionTrackingServiceImpl.generateCacheKey(action); + + + // Check it's in the cache + System.out.println("Checking the cache for " + key); + assertNotNull(executingActionsCache.get(key)); + + ExecutionSummary s = ActionTrackingServiceImpl.buildExecutionSummary(action); + ExecutionDetails d = actionTrackingService.getExecutionDetails(s); + assertNotNull(d.getExecutionSummary()); + assertEquals("sleep-action", d.getActionType()); + assertEquals(actionId, d.getActionId()); + assertEquals(1, d.getExecutionInstance()); + assertEquals(null, d.getPersistedActionRef()); + assertNotNull(null, d.getStartedAt()); - - // Tell it to stop sleeping - // Then wait for it to finish and go bang - // (Need to do it by hand, as it won't fire the complete policy - // as the action has failed) - sleepActionExec.getExecutingThread().interrupt(); - Thread.sleep(150); - - - // Ensure it went away again - assertEquals(ActionStatus.Failed, action.getExecutionStatus()); - assertEquals("Bang!", action.getExecutionFailureMessage()); - assertEquals(null, executingActionsCache.get(key)); - - d = actionTrackingService.getExecutionDetails(s); - assertEquals(null, d); + + // Tell it to stop sleeping + // Then wait for it to finish and go bang + // (Need to do it by hand, as it won't fire the complete policy + // as the action has failed) + sleepActionExec.getExecutingThread().interrupt(); + Thread.sleep(150); + + + // Ensure it went away again + assertEquals(null, executingActionsCache.get(key)); + + d = actionTrackingService.getExecutionDetails(s); + assertEquals(null, d); + + return action; } /** Ensure that pending actions behave properly */ @@ -1238,10 +1256,16 @@ public class ActionTrackingServiceImplTest extends TestCase return failingAction; } - - private Action createFailingSleepAction(String id) throws Exception + + /** + * + * @param id + * @param isFatal true means the sleep action will fail with a RuntimeException, + * false means it will fail with a {@link ActionServiceTransientException}. + */ + private Action createFailingSleepAction(String id, boolean isFatal) throws Exception { - return ActionServiceImplTest.createFailingSleepAction(id, actionService); + return ActionServiceImplTest.createFailingSleepAction(id, isFatal, actionService); } private Action createWorkingSleepAction(String id) throws Exception diff --git a/source/java/org/alfresco/repo/action/actionModel.xml b/source/java/org/alfresco/repo/action/actionModel.xml index b17527367d..686165cb22 100644 --- a/source/java/org/alfresco/repo/action/actionModel.xml +++ b/source/java/org/alfresco/repo/action/actionModel.xml @@ -24,6 +24,7 @@ Running Completed Cancelled + Declined Failed
diff --git a/source/java/org/alfresco/repo/action/evaluator/compare/TextPropertyValueComparator.java b/source/java/org/alfresco/repo/action/evaluator/compare/TextPropertyValueComparator.java index c0283a3aa9..12495f20cc 100644 --- a/source/java/org/alfresco/repo/action/evaluator/compare/TextPropertyValueComparator.java +++ b/source/java/org/alfresco/repo/action/evaluator/compare/TextPropertyValueComparator.java @@ -1,186 +1,177 @@ -/* - * 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.action.evaluator.compare; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.List; - -import org.alfresco.repo.action.evaluator.ComparePropertyValueEvaluator; -import org.alfresco.service.cmr.action.ActionServiceException; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; - -/** - * Test property value comparator - * - * @author Roy Wetherall - */ -public class TextPropertyValueComparator implements PropertyValueComparator -{ - /** - * I18N message ids - */ - private static final String MSGID_INVALID_OPERATION = "text_property_value_comparator.invalid_operation"; - - /** - * Special star string - */ - private static final String STAR = "*"; - - /** - * @see org.alfresco.repo.action.evaluator.compare.PropertyValueComparator#compare(java.io.Serializable, java.io.Serializable, org.alfresco.repo.action.evaluator.compare.ComparePropertyValueOperation) - */ - public boolean compare( - Serializable propertyValue, - Serializable compareValue, - ComparePropertyValueOperation operation) - { - String compareText = (String)compareValue; - - boolean result = false; - if (operation == null) - { - // Check for a trailing or leading star since it implies special behaviour when no default operation is specified - if (compareText.startsWith(STAR) == true) - { - // Remove the star and set the operation to endsWith - operation = ComparePropertyValueOperation.ENDS; - compareText = compareText.substring(1); - } - else if (compareText.endsWith(STAR) == true) - { - // Remove the star and set the operation to startsWith - operation = ComparePropertyValueOperation.BEGINS; - compareText = compareText.substring(0, (compareText.length()-1)); - } - else - { - operation = ComparePropertyValueOperation.CONTAINS; - } - } - - // Build the reg ex - String regEx = buildRegEx(compareText, operation); - - // Do the match - if (propertyValue != null) - { - result = ((String)propertyValue).toLowerCase().matches(regEx); - } - - return result; - } - - /** - * Builds the regular expressin that it used to make the match - * - * @param matchText the raw text to be matched - * @param operation the operation - * @return the regular expression string - */ - private String buildRegEx(String matchText, ComparePropertyValueOperation operation) - { - String result = escapeText(matchText.toLowerCase()); - switch (operation) - { - case CONTAINS: - result = "^.*" + result + ".*$"; - break; - case BEGINS: - result = "^" + result + ".*$"; - break; - case ENDS: - result = "^.*" + result + "$"; - break; - case EQUALS: - break; - default: - // Raise an invalid operation exception - throw new ActionServiceException( - MSGID_INVALID_OPERATION, - new Object[]{operation.toString()}); - } - return result; - } - - /** - * Escapes the text before it is turned into a regualr expression - * - * @param matchText the raw text - * @return the escaped text - */ - private String escapeText(String matchText) - { - StringBuilder builder = new StringBuilder(matchText.length()); - for (char charValue : matchText.toCharArray()) - { - if (charValue == '*') - { - builder.append("."); - } - else if (getEscapeCharList().contains(charValue) == true) - { - builder.append("\\"); - } - builder.append(charValue); - } - - return builder.toString(); - } - - /** - * List of escape characters - */ - private static List ESCAPE_CHAR_LIST = null; - - /** - * Get the list of escape chars - * - * @return list of excape chars - */ - private List getEscapeCharList() - { - if (ESCAPE_CHAR_LIST == null) - { - //([{\^$|)?*+. - ESCAPE_CHAR_LIST = new ArrayList(4); - ESCAPE_CHAR_LIST.add('.'); - ESCAPE_CHAR_LIST.add('^'); - ESCAPE_CHAR_LIST.add('$'); - ESCAPE_CHAR_LIST.add('('); - ESCAPE_CHAR_LIST.add('['); - ESCAPE_CHAR_LIST.add('{'); - ESCAPE_CHAR_LIST.add('\\'); - ESCAPE_CHAR_LIST.add('|'); - ESCAPE_CHAR_LIST.add(')'); - ESCAPE_CHAR_LIST.add('?'); - ESCAPE_CHAR_LIST.add('+'); - } - return ESCAPE_CHAR_LIST; - } - - /** - * @see org.alfresco.repo.action.evaluator.compare.PropertyValueComparator#registerComparator(org.alfresco.repo.action.evaluator.ComparePropertyValueEvaluator) - */ - public void registerComparator(ComparePropertyValueEvaluator evaluator) - { - evaluator.registerComparator(DataTypeDefinition.TEXT, this); - evaluator.registerComparator(DataTypeDefinition.MLTEXT, this); - } -} +/* + * 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.action.evaluator.compare; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +import org.alfresco.repo.action.evaluator.ComparePropertyValueEvaluator; +import org.alfresco.service.cmr.action.ActionServiceException; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; + +/** + * Test property value comparator + * + * @author Roy Wetherall + */ +public class TextPropertyValueComparator implements PropertyValueComparator +{ + /** + * I18N message ids + */ + private static final String MSGID_INVALID_OPERATION = "text_property_value_comparator.invalid_operation"; + + /** + * Special star string + */ + private static final String STAR = "*"; + + /** + * @see org.alfresco.repo.action.evaluator.compare.PropertyValueComparator#compare(java.io.Serializable, java.io.Serializable, org.alfresco.repo.action.evaluator.compare.ComparePropertyValueOperation) + */ + public boolean compare( + Serializable propertyValue, + Serializable compareValue, + ComparePropertyValueOperation operation) + { + String compareText = (String)compareValue; + + boolean result = false; + if (operation == null) + { + // Check for a trailing or leading star since it implies special behaviour when no default operation is specified + if (compareText.startsWith(STAR) == true) + { + // Remove the star and set the operation to endsWith + operation = ComparePropertyValueOperation.ENDS; + compareText = compareText.substring(1); + } + else if (compareText.endsWith(STAR) == true) + { + // Remove the star and set the operation to startsWith + operation = ComparePropertyValueOperation.BEGINS; + compareText = compareText.substring(0, (compareText.length()-1)); + } + else + { + operation = ComparePropertyValueOperation.CONTAINS; + } + } + + // Build the reg ex + String regEx = buildRegEx(compareText, operation); + + // Do the match + if (propertyValue != null) + { + result = ((String)propertyValue).toLowerCase().matches(regEx); + } + + return result; + } + + /** + * Builds the regular expressin that it used to make the match + * + * @param matchText the raw text to be matched + * @param operation the operation + * @return the regular expression string + */ + private String buildRegEx(String matchText, ComparePropertyValueOperation operation) + { + String result = escapeText(matchText.toLowerCase()); + switch (operation) + { + case CONTAINS: + result = "^.*" + result + ".*$"; + break; + case BEGINS: + result = "^" + result + ".*$"; + break; + case ENDS: + result = "^.*" + result + "$"; + break; + case EQUALS: + break; + default: + // Raise an invalid operation exception + throw new ActionServiceException( + MSGID_INVALID_OPERATION, + new Object[]{operation.toString()}); + } + return result; + } + + /** + * Escapes the text before it is turned into a regualr expression + * + * @param matchText the raw text + * @return the escaped text + */ + private String escapeText(String matchText) + { + StringBuilder builder = new StringBuilder(matchText.length()); + for (char charValue : matchText.toCharArray()) + { + if (charValue == '*') + { + builder.append("."); + } + else if (ESCAPE_CHAR_LIST.contains(charValue) == true) + { + builder.append("\\"); + } + builder.append(charValue); + } + + return builder.toString(); + } + + /** + * List of escape characters + */ + private static List ESCAPE_CHAR_LIST = null; + + static + { + // ([{\^$|)?*+. + ESCAPE_CHAR_LIST = new ArrayList(4); + ESCAPE_CHAR_LIST.add('.'); + ESCAPE_CHAR_LIST.add('^'); + ESCAPE_CHAR_LIST.add('$'); + ESCAPE_CHAR_LIST.add('('); + ESCAPE_CHAR_LIST.add('['); + ESCAPE_CHAR_LIST.add('{'); + ESCAPE_CHAR_LIST.add('\\'); + ESCAPE_CHAR_LIST.add('|'); + ESCAPE_CHAR_LIST.add(')'); + ESCAPE_CHAR_LIST.add('?'); + ESCAPE_CHAR_LIST.add('+'); + } + + /** + * @see org.alfresco.repo.action.evaluator.compare.PropertyValueComparator#registerComparator(org.alfresco.repo.action.evaluator.ComparePropertyValueEvaluator) + */ + public void registerComparator(ComparePropertyValueEvaluator evaluator) + { + evaluator.registerComparator(DataTypeDefinition.TEXT, this); + evaluator.registerComparator(DataTypeDefinition.MLTEXT, this); + } +} diff --git a/source/java/org/alfresco/repo/action/executer/MailActionExecuter.java b/source/java/org/alfresco/repo/action/executer/MailActionExecuter.java index 4cbf2a7e2d..dcea0fcd1c 100644 --- a/source/java/org/alfresco/repo/action/executer/MailActionExecuter.java +++ b/source/java/org/alfresco/repo/action/executer/MailActionExecuter.java @@ -32,6 +32,7 @@ import javax.mail.internet.MimeMessage; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.repo.action.ParameterDefinitionImpl; +import org.alfresco.repo.admin.SysAdminParams; import org.alfresco.repo.template.DateCompareMethod; import org.alfresco.repo.template.HasAspectMethod; import org.alfresco.repo.template.I18NMessageMethod; @@ -92,8 +93,6 @@ public class MailActionExecuter extends ActionExecuterAbstractBase */ private static final String FROM_ADDRESS = "alfresco@alfresco.org"; - private static final String REPO_REMOTE_URL = "http://localhost:8080/alfresco"; - /** * The java mail sender */ @@ -129,6 +128,11 @@ public class MailActionExecuter extends ActionExecuterAbstractBase */ private ServiceRegistry serviceRegistry; + /** + * System Administration parameters, including URL information + */ + private SysAdminParams sysAdminParams; + /** * Mail header encoding scheme */ @@ -137,12 +141,13 @@ public class MailActionExecuter extends ActionExecuterAbstractBase /** * Default from address */ - private String fromAddress = null; + private String fromDefaultAddress = null; /** - * Default alfresco installation url + * Is the from field enabled? Or must we always use the default address. */ - private String repoRemoteUrl = null; + private boolean fromEnabled = true; + private boolean sendTestMessage = false; private String testMessageTo = null; @@ -228,16 +233,13 @@ public class MailActionExecuter extends ActionExecuterAbstractBase */ public void setFromAddress(String fromAddress) { - this.fromAddress = fromAddress; + this.fromDefaultAddress = fromAddress; } - /** - * - * @param repoRemoteUrl The default alfresco installation url - */ - public void setRepoRemoteUrl(String repoRemoteUrl) + + public void setSysAdminParams(SysAdminParams sysAdminParams) { - this.repoRemoteUrl = repoRemoteUrl; + this.sysAdminParams = sysAdminParams; } public void setTestMessageTo(String testMessageTo) @@ -282,15 +284,11 @@ public class MailActionExecuter extends ActionExecuterAbstractBase */ public void afterPropertiesSet() throws Exception { - if (fromAddress == null || fromAddress.length() == 0) + if (fromDefaultAddress == null || fromDefaultAddress.length() == 0) { - fromAddress = FROM_ADDRESS; + fromDefaultAddress = FROM_ADDRESS; } - if (repoRemoteUrl == null || repoRemoteUrl.length() == 0) - { - repoRemoteUrl = REPO_REMOTE_URL; - } } /** @@ -472,11 +470,63 @@ public class MailActionExecuter extends ActionExecuterAbstractBase // from person NodeRef fromPerson = null; + + // from is enabled if (! authService.isCurrentUserTheSystemUser()) { fromPerson = personService.getPerson(authService.getCurrentUserName()); } + if(isFromEnabled()) + { + // Use the FROM parameter in preference to calculating values. + String from = (String)ruleAction.getParameterValue(PARAM_FROM); + if (from != null && from.length() > 0) + { + if(logger.isDebugEnabled()) + { + logger.debug("from specified as a parameter, from:" + from); + } + message.setFrom(from); + } + else + { + // set the from address from the current user + String fromActualUser = null; + if (fromPerson != null) + { + fromActualUser = (String) nodeService.getProperty(fromPerson, ContentModel.PROP_EMAIL); + } + + if (fromActualUser != null && fromActualUser.length() != 0) + { + if(logger.isDebugEnabled()) + { + logger.debug("looked up email address for :" + fromPerson + " email from " + fromActualUser); + } + message.setFrom(fromActualUser); + } + else + { + // from system or user does not have email address + message.setFrom(fromDefaultAddress); + } + } + + } + else + { + if(logger.isDebugEnabled()) + { + logger.debug("from not enabled - sending from default address:" + fromDefaultAddress); + } + // from is not enabled. + message.setFrom(fromDefaultAddress); + } + + + + // set subject line message.setSubject((String)ruleAction.getParameterValue(PARAM_SUBJECT)); @@ -540,29 +590,6 @@ public class MailActionExecuter extends ActionExecuterAbstractBase message.setText(text, isHTML); } - // set the from address - String fromActualUser = null; - if (fromPerson != null) - { - fromActualUser = (String) nodeService.getProperty(fromPerson, ContentModel.PROP_EMAIL); - } - - if (fromActualUser != null && fromActualUser.length() != 0) - { - message.setFrom(fromActualUser); - } - else - { - String from = (String)ruleAction.getParameterValue(PARAM_FROM); - if (from == null || from.length() == 0) - { - message.setFrom(fromAddress); - } - else - { - message.setFrom(from); - } - } } }; @@ -662,7 +689,7 @@ public class MailActionExecuter extends ActionExecuterAbstractBase model.put("dateCompare", new DateCompareMethod()); // add URLs - model.put("url", new URLHelper(repoRemoteUrl)); + model.put("url", new URLHelper(sysAdminParams)); model.put(TemplateService.KEY_SHARE_URL, UrlUtil.getShareUrl(this.serviceRegistry.getSysAdminParams())); // if the caller specified a model, use it without overriding @@ -732,26 +759,34 @@ public class MailActionExecuter extends ActionExecuterAbstractBase lastTestMessage = null; } + public void setFromEnabled(boolean fromEnabled) + { + this.fromEnabled = fromEnabled; + } + + public boolean isFromEnabled() + { + return fromEnabled; + } + public static class URLHelper { - String contextPath; - String serverPath; + private final SysAdminParams sysAdminParams; - public URLHelper(String repoRemoteUrl) + public URLHelper(SysAdminParams sysAdminParams) { - String[] parts = repoRemoteUrl.split("/"); - this.contextPath = "/" + parts[parts.length - 1]; - this.serverPath = parts[0] + "//" + parts[2]; + this.sysAdminParams = sysAdminParams; } public String getContext() { - return this.contextPath; + return "/" + sysAdminParams.getAlfrescoContext(); } public String getServerPath() { - return this.serverPath; + return sysAdminParams.getAlfrescoProtocol() + "://" + sysAdminParams.getAlfrescoHost() + ":" + + sysAdminParams.getAlfrescoPort(); } } } diff --git a/source/java/org/alfresco/repo/action/test-action-services-context.xml b/source/java/org/alfresco/repo/action/test-action-services-context.xml index feb7971bb9..882e19ac6e 100644 --- a/source/java/org/alfresco/repo/action/test-action-services-context.xml +++ b/source/java/org/alfresco/repo/action/test-action-services-context.xml @@ -20,4 +20,8 @@ sleep-action
+ + diff --git a/source/java/org/alfresco/repo/activities/feed/AbstractFeedGenerator.java b/source/java/org/alfresco/repo/activities/feed/AbstractFeedGenerator.java index 1008cec858..870f18bdba 100644 --- a/source/java/org/alfresco/repo/activities/feed/AbstractFeedGenerator.java +++ b/source/java/org/alfresco/repo/activities/feed/AbstractFeedGenerator.java @@ -19,6 +19,7 @@ package org.alfresco.repo.activities.feed; import org.alfresco.repo.activities.ActivityPostServiceImpl; +import org.alfresco.repo.admin.SysAdminParams; import org.alfresco.repo.domain.activities.ActivityPostDAO; import org.alfresco.repo.lock.JobLockService; import org.alfresco.repo.lock.JobLockService.JobLockRefreshCallback; @@ -53,6 +54,7 @@ public abstract class AbstractFeedGenerator implements FeedGenerator private ActivityPostDAO postDAO; private ActivityPostServiceImpl activityPostServiceImpl; private AuthenticationService authenticationService; + private SysAdminParams sysAdminParams; private TransactionService transactionService; private JobLockService jobLockService; @@ -114,6 +116,11 @@ public abstract class AbstractFeedGenerator implements FeedGenerator this.jobLockService = jobLockService; } + public void setSysAdminParams(SysAdminParams sysAdminParams) + { + this.sysAdminParams = sysAdminParams; + } + public void setTransactionService(TransactionService transactionService) { this.transactionService = transactionService; @@ -126,7 +133,7 @@ public abstract class AbstractFeedGenerator implements FeedGenerator public void init() throws Exception { - ctx = new RepoCtx(repoEndPoint); + ctx = new RepoCtx(sysAdminParams, repoEndPoint); ctx.setUserNamesAreCaseSensitive(userNamesAreCaseSensitive); busy = false; diff --git a/source/java/org/alfresco/repo/activities/feed/AbstractUserNotifier.java b/source/java/org/alfresco/repo/activities/feed/AbstractUserNotifier.java new file mode 100644 index 0000000000..88c95ff7cd --- /dev/null +++ b/source/java/org/alfresco/repo/activities/feed/AbstractUserNotifier.java @@ -0,0 +1,201 @@ +package org.alfresco.repo.activities.feed; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.domain.activities.ActivityFeedEntity; +import org.alfresco.service.cmr.activities.ActivityService; +import org.alfresco.service.cmr.admin.RepoAdminService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.TemplateService; +import org.alfresco.service.cmr.site.SiteInfo; +import org.alfresco.service.cmr.site.SiteService; +import org.alfresco.service.namespace.NamespaceException; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.ModelUtil; +import org.alfresco.util.Pair; +import org.alfresco.util.PropertyCheck; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.json.JSONException; + +/** + * + * @since 4.0 + * + */ +public abstract class AbstractUserNotifier implements UserNotifier +{ + protected static Log logger = LogFactory.getLog(FeedNotifier.class); + + protected ActivityService activityService; + protected NamespaceService namespaceService; + protected RepoAdminService repoAdminService; + protected NodeService nodeService; + protected SiteService siteService; + + public void setActivityService(ActivityService activityService) + { + this.activityService = activityService; + } + + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + public void setRepoAdminService(RepoAdminService repoAdminService) + { + this.repoAdminService = repoAdminService; + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setSiteService(SiteService siteService) + { + this.siteService = siteService; + } + + /** + * Perform basic checks to ensure that the necessary dependencies were injected. + */ + protected void checkProperties() + { + PropertyCheck.mandatory(this, "activityService", activityService); + PropertyCheck.mandatory(this, "nodeService", nodeService); + PropertyCheck.mandatory(this, "namespaceService", namespaceService); + PropertyCheck.mandatory(this, "siteService", siteService); + } + + protected abstract boolean skipUser(NodeRef personNodeRef); + protected abstract Long getFeedId(NodeRef personNodeRef); + protected abstract void notifyUser(NodeRef personNodeRef, String subjectText, Map model, NodeRef templateNodeRef); + + private void addSiteName(String siteId, Map siteNames) + { + if (siteId == null) + { + return; + } + + String siteName = siteNames.get(siteId); + if (siteName == null) + { + SiteInfo site = siteService.getSite(siteId); + if (site == null) + { + return; + } + + String siteTitle = site.getTitle(); + if (siteTitle != null && siteTitle.length() > 0) + { + siteName = siteTitle; + } + else + { + siteName = siteId; + } + + siteNames.put(siteId, siteName); + } + } + + public Pair notifyUser(final NodeRef personNodeRef, String subjectText, Map siteNames, + String shareUrl, int repeatIntervalMins, NodeRef templateNodeRef) + { + Map personProps = nodeService.getProperties(personNodeRef); + + String feedUserId = (String)personProps.get(ContentModel.PROP_USERNAME); + + if (skipUser(personNodeRef)) + { + // skip + return null; + } + + // where did we get up to ? + Long feedDBID = getFeedId(personNodeRef); + + // own + others (note: template can be changed to filter out user's own activities if needed) + List feedEntries = activityService.getUserFeedEntries(feedUserId, FeedTaskProcessor.FEED_FORMAT_JSON, null, false, false, null, null, feedDBID); + + if (feedEntries.size() > 0) + { + long userMaxFeedId = -1L; + + Map model = new HashMap(); + List> activityFeedModels = new ArrayList>(); + + for (ActivityFeedEntity feedEntry : feedEntries) + { + Map map = null; + try + { + map = feedEntry.getModel(); + activityFeedModels.add(map); + + String siteId = feedEntry.getSiteNetwork(); + addSiteName(siteId, siteNames); + + long feedId = feedEntry.getId(); + if (feedId > userMaxFeedId) + { + userMaxFeedId = feedId; + } + } + catch (JSONException je) + { + // skip this feed entry + logger.warn("Skip feed entry for user ("+feedUserId+"): " + je.getMessage()); + continue; + } + } + + if (activityFeedModels.size() > 0) + { + model.put("activities", activityFeedModels); + model.put("siteTitles", siteNames); + model.put("repeatIntervalMins", repeatIntervalMins); + model.put("feedItemsMax", activityService.getMaxFeedItems()); + model.put("feedItemsCount", activityFeedModels.size()); + + // add Share info to model + model.put(TemplateService.KEY_PRODUCT_NAME, ModelUtil.getProductName(repoAdminService)); + + Map personPrefixProps = new HashMap(personProps.size()); + for (QName propQName : personProps.keySet()) + { + try + { + String propPrefix = propQName.toPrefixString(namespaceService); + personPrefixProps.put(propPrefix, personProps.get(propQName)); + } + catch (NamespaceException ne) + { + // ignore properties that do not have a registered namespace + logger.warn("Ignoring property '" + propQName + "' as it's namespace is not registered"); + } + } + + model.put("personProps", personPrefixProps); + + // send + notifyUser(personNodeRef, subjectText, model, templateNodeRef); + + return new Pair(activityFeedModels.size(), userMaxFeedId); + } + } + + return null; + } +} diff --git a/source/java/org/alfresco/repo/activities/feed/EmailUserNotifier.java b/source/java/org/alfresco/repo/activities/feed/EmailUserNotifier.java new file mode 100644 index 0000000000..a5e77715f2 --- /dev/null +++ b/source/java/org/alfresco/repo/activities/feed/EmailUserNotifier.java @@ -0,0 +1,164 @@ +package org.alfresco.repo.activities.feed; + +import java.io.Serializable; +import java.util.List; +import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.action.executer.MailActionExecuter; +import org.alfresco.repo.security.authentication.AuthenticationContext; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ActionService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.ParameterCheck; +import org.alfresco.util.PropertyCheck; +import org.apache.commons.logging.Log; +import org.springframework.beans.factory.InitializingBean; + +/** + * Notifies the given user by sending activity feed information to their registered email address. + * + * @since 4.0 + */ +public class EmailUserNotifier extends AbstractUserNotifier implements InitializingBean +{ + private List excludedEmailSuffixes; + + private AuthenticationContext authenticationContext; + private ActionService actionService; + + public void setAuthenticationContext(AuthenticationContext authenticationContext) + { + this.authenticationContext = authenticationContext; + } + + public void setActionService(ActionService actionService) + { + this.actionService = actionService; + } + + public static Log getLogger() + { + return logger; + } + + public static void setLogger(Log logger) + { + EmailUserNotifier.logger = logger; + } + + public List getExcludedEmailSuffixes() + { + return excludedEmailSuffixes; + } + + public void setExcludedEmailSuffixes(List excludedEmailSuffixes) + { + this.excludedEmailSuffixes = excludedEmailSuffixes; + } + + /** + * Perform basic checks to ensure that the necessary dependencies were injected. + */ + protected void checkProperties() + { + super.checkProperties(); + + PropertyCheck.mandatory(this, "authenticationContext", authenticationContext); + PropertyCheck.mandatory(this, "actionService", actionService); + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() + */ + public void afterPropertiesSet() throws Exception + { + checkProperties(); + } + + protected boolean skipUser(NodeRef personNodeRef) + { + Map personProps = nodeService.getProperties(personNodeRef); + String feedUserId = (String)personProps.get(ContentModel.PROP_USERNAME); + String emailAddress = (String)personProps.get(ContentModel.PROP_EMAIL); + Boolean emailFeedDisabled = (Boolean)personProps.get(ContentModel.PROP_EMAIL_FEED_DISABLED); + + if ((emailFeedDisabled != null) && (emailFeedDisabled == true)) + { + return true; + } + + if (authenticationContext.isSystemUserName(feedUserId) || authenticationContext.isGuestUserName(feedUserId)) + { + // skip "guest" or "System" user + return true; + } + + if ((emailAddress == null) || (emailAddress.length() <= 0)) + { + // skip user that does not have an email address + if (logger.isDebugEnabled()) + { + logger.debug("Skip for '"+feedUserId+"' since they have no email address set"); + } + return true; + } + + String lowerEmailAddress = emailAddress.toLowerCase(); + for (String excludedEmailSuffix : excludedEmailSuffixes) + { + if (lowerEmailAddress.endsWith(excludedEmailSuffix.toLowerCase())) + { + // skip user whose email matches exclude suffix + if (logger.isDebugEnabled()) + { + logger.debug("Skip for '"+feedUserId+"' since email address is excluded ("+emailAddress+")"); + } + return true; + } + } + + return false; + } + + protected Long getFeedId(NodeRef personNodeRef) + { + Map personProps = nodeService.getProperties(personNodeRef); + + // where did we get up to ? + Long emailFeedDBID = (Long)personProps.get(ContentModel.PROP_EMAIL_FEED_ID); + if (emailFeedDBID != null) + { + // increment min feed id + emailFeedDBID++; + } + else + { + emailFeedDBID = -1L; + } + + return emailFeedDBID; + } + + protected void notifyUser(NodeRef personNodeRef, String subjectText, Map model, NodeRef templateNodeRef) + { + ParameterCheck.mandatory("personNodeRef", personNodeRef); + + Map personProps = nodeService.getProperties(personNodeRef); + String emailAddress = (String)personProps.get(ContentModel.PROP_EMAIL); + + Action mail = actionService.createAction(MailActionExecuter.NAME); + + mail.setParameterValue(MailActionExecuter.PARAM_TO, emailAddress); + mail.setParameterValue(MailActionExecuter.PARAM_SUBJECT, subjectText); + + //mail.setParameterValue(MailActionExecuter.PARAM_TEXT, buildMailText(emailTemplateRef, model)); + mail.setParameterValue(MailActionExecuter.PARAM_TEMPLATE, templateNodeRef); + mail.setParameterValue(MailActionExecuter.PARAM_TEMPLATE_MODEL, (Serializable)model); + + actionService.executeAction(mail, null); + } + +} diff --git a/source/java/org/alfresco/repo/activities/feed/FeedNotifierImpl.java b/source/java/org/alfresco/repo/activities/feed/FeedNotifierImpl.java index 68a733c561..68a3d26d14 100644 --- a/source/java/org/alfresco/repo/activities/feed/FeedNotifierImpl.java +++ b/source/java/org/alfresco/repo/activities/feed/FeedNotifierImpl.java @@ -1,68 +1,45 @@ -/* - * 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.activities.feed; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.HashMap; +import java.util.Collection; import java.util.List; import java.util.Map; -import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; import org.alfresco.model.ContentModel; -import org.alfresco.repo.action.executer.MailActionExecuter; +import org.alfresco.query.PagingRequest; +import org.alfresco.query.PagingResults; import org.alfresco.repo.admin.SysAdminParams; +import org.alfresco.repo.batch.BatchProcessWorkProvider; +import org.alfresco.repo.batch.BatchProcessor; import org.alfresco.repo.dictionary.RepositoryLocation; -import org.alfresco.repo.domain.activities.ActivityFeedEntity; import org.alfresco.repo.lock.JobLockService; import org.alfresco.repo.lock.LockAcquisitionException; -import org.alfresco.repo.security.authentication.AuthenticationContext; +import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.transaction.RetryingTransactionHelper; -import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; -import org.alfresco.service.cmr.action.Action; -import org.alfresco.service.cmr.action.ActionService; -import org.alfresco.service.cmr.activities.ActivityService; import org.alfresco.service.cmr.admin.RepoAdminService; import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.repository.InvalidNodeRefException; 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.cmr.repository.TemplateService; import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.cmr.security.PersonService; -import org.alfresco.service.cmr.site.SiteInfo; -import org.alfresco.service.cmr.site.SiteService; -import org.alfresco.service.namespace.NamespaceException; +import org.alfresco.service.cmr.security.PersonService.PersonInfo; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.ModelUtil; import org.alfresco.util.Pair; -import org.alfresco.util.ParameterCheck; import org.alfresco.util.PropertyCheck; import org.alfresco.util.UrlUtil; import org.alfresco.util.VmShutdownListener; import org.alfresco.util.VmShutdownListener.VmShutdownException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.json.JSONException; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; import org.springframework.extensions.surf.util.I18NUtil; /** @@ -70,43 +47,67 @@ import org.springframework.extensions.surf.util.I18NUtil; * * Note: currently implemented to email activities stored in JSON format * - * @since 3.5 + * @since 4.0 */ -public class FeedNotifierImpl implements FeedNotifier +public class FeedNotifierImpl implements FeedNotifier, ApplicationContextAware { - private static Log logger = LogFactory.getLog(FeedNotifierImpl.class); + protected static Log logger = LogFactory.getLog(FeedNotifier.class); private static final QName LOCK_QNAME = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "ActivityFeedNotifier"); private static final long LOCK_TTL = 30000L; - private static ThreadLocal> lockThreadLocal = new ThreadLocal>(); private static VmShutdownListener vmShutdownListener = new VmShutdownListener(FeedNotifierImpl.class.getName()); private static final String MSG_EMAIL_SUBJECT = "activities.feed.notifier.email.subject"; - private ActivityService activityService; + private NamespaceService namespaceService; + private FileFolderService fileFolderService; + private SearchService searchService; private PersonService personService; private NodeService nodeService; - private FileFolderService fileFolderService; - private ActionService actionService; - private SearchService searchService; - private NamespaceService namespaceService; - private SiteService siteService; private JobLockService jobLockService; private TransactionService transactionService; - private AuthenticationContext authenticationContext; private SysAdminParams sysAdminParams; private RepoAdminService repoAdminService; - private List excludedEmailSuffixes; + private UserNotifier userNotifier; + + private ApplicationContext applicationContext; private RepositoryLocation feedEmailTemplateLocation; + private int numThreads = 4; + private int batchSize = 200; - public void setActivityService(ActivityService activityService) + public void setNumThreads(int numThreads) { - this.activityService = activityService; + this.numThreads = numThreads; } + public void setBatchSize(int batchSize) + { + this.batchSize = batchSize; + } + + public void setUserNotifier(UserNotifier userNotifier) + { + this.userNotifier = userNotifier; + } + + public void setFileFolderService(FileFolderService fileFolderService) + { + this.fileFolderService = fileFolderService; + } + + public void setSearchService(SearchService searchService) + { + this.searchService = searchService; + } + + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + public void setPersonService(PersonService personService) { this.personService = personService; @@ -116,32 +117,7 @@ public class FeedNotifierImpl implements FeedNotifier { this.nodeService = nodeService; } - - public void setFileFolderService(FileFolderService fileFolderService) - { - this.fileFolderService = fileFolderService; - } - - public void setActionService(ActionService actionService) - { - this.actionService = actionService; - } - - public void setSearchService(SearchService searchService) - { - this.searchService = searchService; - } - - public void setNamespaceService(NamespaceService namespaceService) - { - this.namespaceService = namespaceService; - } - - public void setSiteService(SiteService siteService) - { - this.siteService = siteService; - } - + public void setJobLockService(JobLockService jobLockService) { this.jobLockService = jobLockService; @@ -152,16 +128,6 @@ public class FeedNotifierImpl implements FeedNotifier this.transactionService = transactionService; } - public void setAuthenticationContext(AuthenticationContext authenticationContext) - { - this.authenticationContext = authenticationContext; - } - - public void setFeedEmailTemplateLocation(RepositoryLocation feedEmailTemplateLocation) - { - this.feedEmailTemplateLocation = feedEmailTemplateLocation; - } - public void setSysAdminParams(SysAdminParams sysAdminParams) { this.sysAdminParams = sysAdminParams; @@ -172,29 +138,16 @@ public class FeedNotifierImpl implements FeedNotifier this.repoAdminService = repoAdminService; } - public void setExcludedEmailSuffixes(List excludedEmailSuffixes) - { - this.excludedEmailSuffixes = excludedEmailSuffixes; - } - /** * Perform basic checks to ensure that the necessary dependencies were injected. */ - private void checkProperties() + protected void checkProperties() { - PropertyCheck.mandatory(this, "activityService", activityService); PropertyCheck.mandatory(this, "personService", personService); PropertyCheck.mandatory(this, "nodeService", nodeService); - PropertyCheck.mandatory(this, "fileFolderService", fileFolderService); - PropertyCheck.mandatory(this, "actionService", actionService); - PropertyCheck.mandatory(this, "searchService", searchService); - PropertyCheck.mandatory(this, "namespaceService", namespaceService); - PropertyCheck.mandatory(this, "siteService", siteService); PropertyCheck.mandatory(this, "jobLockService", jobLockService); PropertyCheck.mandatory(this, "transactionService", transactionService); - PropertyCheck.mandatory(this, "authenticationContext", authenticationContext); PropertyCheck.mandatory(this, "sysAdminParams", sysAdminParams); - PropertyCheck.mandatory(this, "feedEmailTemplateLocation", feedEmailTemplateLocation); } public void execute(int repeatIntervalMins) @@ -210,16 +163,17 @@ public class FeedNotifierImpl implements FeedNotifier } return; } - + + String lockToken = getLock(LOCK_TTL); + try { if (logger.isTraceEnabled()) { logger.trace("Activities email notification started"); } - - refreshLock(); - executeInternal(repeatIntervalMins); + + executeInternal(lockToken, repeatIntervalMins); // Done if (logger.isTraceEnabled()) @@ -245,250 +199,16 @@ public class FeedNotifierImpl implements FeedNotifier } finally { - releaseLock(); + releaseLock(lockToken); } } - - private void executeInternal(final int repeatIntervalMins) + + public void setFeedEmailTemplateLocation(RepositoryLocation feedEmailTemplateLocation) { - final NodeRef emailTemplateRef = getEmailTemplateRef(); - - if (emailTemplateRef == null) - { - return; - } - - final String shareUrl = UrlUtil.getShareUrl(sysAdminParams); - - if (logger.isDebugEnabled()) - { - logger.debug("Share URL configured as: "+shareUrl); - } - - int userCnt = 0; - int feedEntryCnt = 0; - - long startTime = System.currentTimeMillis(); - - try - { - final String subjectText = buildSubjectText(startTime); - - Set people = personService.getAllPeople(); - - // local cache for this execution - final Map siteNames = new HashMap(10); - - for (final NodeRef personNodeRef : people) - { - refreshLock(); - - try - { - final RetryingTransactionHelper txHelper = transactionService.getRetryingTransactionHelper(); - txHelper.setMaxRetries(0); - - Pair result = txHelper.doInTransaction(new RetryingTransactionCallback>() - { - public Pair execute() throws Throwable - { - return prepareAndSendEmail(personNodeRef, emailTemplateRef, subjectText, siteNames, shareUrl, repeatIntervalMins); - } - }, true, true); - - if (result != null) - { - int entryCnt = result.getFirst(); - final long maxFeedId = result.getSecond(); - - transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() - { - public Void execute() throws Throwable - { - - Long currentMaxFeedId = (Long)nodeService.getProperty(personNodeRef, ContentModel.PROP_EMAIL_FEED_ID); - if ((currentMaxFeedId == null) || (currentMaxFeedId < maxFeedId)) - { - nodeService.setProperty(personNodeRef, ContentModel.PROP_EMAIL_FEED_ID, maxFeedId); - } - - return null; - } - }, false, true); - - - userCnt++; - feedEntryCnt += entryCnt; - } - } - catch (InvalidNodeRefException inre) - { - // skip this person - eg. no longer exists ? - logger.warn("Skip feed notification for user ("+personNodeRef+"): " + inre.getMessage()); - } - } - } - catch (Throwable e) - { - // If the VM is shutting down, then ignore - if (vmShutdownListener.isVmShuttingDown()) - { - // Ignore - } - else - { - logger.error("Exception during notification of feeds", e); - } - } - finally - { - // assume sends are synchronous - hence bump up to last max feed id - if (userCnt > 0) - { - if (logger.isInfoEnabled()) - { - // TODO i18n of info message - StringBuilder sb = new StringBuilder(); - sb.append("Notified ").append(userCnt).append(" user").append(userCnt != 1 ? "s" : ""); - sb.append(" of ").append(feedEntryCnt).append(" activity feed entr").append(feedEntryCnt != 1 ? "ies" : "y"); - sb.append(" (in ").append(System.currentTimeMillis()-startTime).append(" msecs)"); - logger.info(sb.toString()); - } - } - else - { - if (logger.isTraceEnabled()) - { - logger.trace("Nothing to send since no new user activities found"); - } - } - } + this.feedEmailTemplateLocation = feedEmailTemplateLocation; } - protected Pair prepareAndSendEmail(final NodeRef personNodeRef, NodeRef emailTemplateRef, - String subjectText, Map siteNames, - String shareUrl, int repeatIntervalMins) - { - Map personProps = nodeService.getProperties(personNodeRef); - - String feedUserId = (String)personProps.get(ContentModel.PROP_USERNAME); - String emailAddress = (String)personProps.get(ContentModel.PROP_EMAIL); - Boolean emailFeedDisabled = (Boolean)personProps.get(ContentModel.PROP_EMAIL_FEED_DISABLED); - - if (skipUser(emailFeedDisabled, feedUserId, emailAddress, excludedEmailSuffixes)) - { - // skip - return null; - } - - // where did we get up to ? - Long emailFeedDBID = (Long)personProps.get(ContentModel.PROP_EMAIL_FEED_ID); - if (emailFeedDBID != null) - { - // increment min feed id - emailFeedDBID++; - } - else - { - emailFeedDBID = -1L; - } - - - // own + others (note: template can be changed to filter out user's own activities if needed) - List feedEntries = activityService.getUserFeedEntries(feedUserId, FeedTaskProcessor.FEED_FORMAT_JSON, null, false, false, null, null, emailFeedDBID); - - if (feedEntries.size() > 0) - { - long userMaxFeedId = -1L; - - Map model = new HashMap(); - List> activityFeedModels = new ArrayList>(); - - for (ActivityFeedEntity feedEntry : feedEntries) - { - Map map = null; - try - { - map = feedEntry.getModel(); - activityFeedModels.add(map); - - String siteId = feedEntry.getSiteNetwork(); - addSiteName(siteId, siteNames); - - long feedId = feedEntry.getId(); - if (feedId > userMaxFeedId) - { - userMaxFeedId = feedId; - } - } - catch (JSONException je) - { - // skip this feed entry - logger.warn("Skip feed entry for user ("+feedUserId+"): " + je.getMessage()); - continue; - } - } - - if (activityFeedModels.size() > 0) - { - model.put("activities", activityFeedModels); - model.put("siteTitles", siteNames); - model.put("repeatIntervalMins", repeatIntervalMins); - model.put("feedItemsMax", activityService.getMaxFeedItems()); - model.put("feedItemsCount", activityFeedModels.size()); - - // add Share info to model - model.put(TemplateService.KEY_PRODUCT_NAME, ModelUtil.getProductName(repoAdminService)); - - Map personPrefixProps = new HashMap(personProps.size()); - for (QName propQName : personProps.keySet()) - { - try - { - String propPrefix = propQName.toPrefixString(namespaceService); - personPrefixProps.put(propPrefix, personProps.get(propQName)); - } - catch (NamespaceException ne) - { - // ignore properties that do not have a registered namespace - logger.warn("Ignoring property '" + propQName + "' as it's namespace is not registered"); - } - } - - model.put("personProps", personPrefixProps); - - // send - sendMail(emailTemplateRef, emailAddress, subjectText, model); - - return new Pair(activityFeedModels.size(), userMaxFeedId); - } - } - - return null; - } - - protected void sendMail(NodeRef emailTemplateRef, String emailAddress, String subjectText, Map model) - { - ParameterCheck.mandatoryString("emailAddress", emailAddress); - - Action mail = actionService.createAction(MailActionExecuter.NAME); - - mail.setParameterValue(MailActionExecuter.PARAM_TO, emailAddress); - mail.setParameterValue(MailActionExecuter.PARAM_SUBJECT, subjectText); - - //mail.setParameterValue(MailActionExecuter.PARAM_TEXT, buildMailText(emailTemplateRef, model)); - mail.setParameterValue(MailActionExecuter.PARAM_TEMPLATE, emailTemplateRef); - mail.setParameterValue(MailActionExecuter.PARAM_TEMPLATE_MODEL, (Serializable)model); - - actionService.executeAction(mail, null); - } - - protected String buildSubjectText(long currentTime) - { - return I18NUtil.getMessage(MSG_EMAIL_SUBJECT, ModelUtil.getProductName(repoAdminService)); - } - - protected NodeRef getEmailTemplateRef() + private NodeRef getEmailTemplateRef() { StoreRef store = feedEmailTemplateLocation.getStoreRef(); String xpath = feedEmailTemplateLocation.getPath(); @@ -505,128 +225,203 @@ public class FeedNotifierImpl implements FeedNotifier logger.warn("Cannot find the activities email template: "+xpath); return null; } - + return fileFolderService.getLocalizedSibling(nodeRefs.get(0)); } - protected void addSiteName(String siteId, Map siteNames) + private void executeInternal(final String lockToken, final int repeatIntervalMins) { - if (siteId == null) + final NodeRef emailTemplateRef = getEmailTemplateRef(); + + if (emailTemplateRef == null) { return; } - - String siteName = siteNames.get(siteId); - if (siteName == null) + + final String shareUrl = UrlUtil.getShareUrl(sysAdminParams); + + if (logger.isDebugEnabled()) { - SiteInfo site = siteService.getSite(siteId); - if (site == null) + logger.debug("Share URL configured as: "+shareUrl); + } + + final AtomicInteger userCnt = new AtomicInteger(0); + final AtomicInteger feedEntryCnt = new AtomicInteger(0); + + long startTime = System.currentTimeMillis(); + + // local cache for this execution + final Map siteNames = new ConcurrentHashMap(10); + + try + { + final String subjectText = buildSubjectText(startTime); + final String currentUser = AuthenticationUtil.getFullyAuthenticatedUser(); + + // process the feeds using the batch processor {@link BatchProcessor} + BatchProcessor.BatchProcessWorker worker = new BatchProcessor.BatchProcessWorker() + { + public String getIdentifier(final PersonInfo person) + { + StringBuilder sb = new StringBuilder("Person "); + sb.append(person.getUserName()); + return sb.toString(); + } + + public void beforeProcess() throws Throwable + { + AuthenticationUtil.setRunAsUser(currentUser); + + refreshLock(lockToken, batchSize * 500L); + } + + public void afterProcess() throws Throwable + { + } + + public void process(final PersonInfo person) throws Throwable + { + final NodeRef personNodeRef = person.getNodeRef(); + + try + { + Pair result = userNotifier.notifyUser(personNodeRef, subjectText, siteNames, shareUrl, repeatIntervalMins, emailTemplateRef); + if (result != null) + { + int entryCnt = result.getFirst(); + final long maxFeedId = result.getSecond(); + + Long currentMaxFeedId = (Long)nodeService.getProperty(personNodeRef, ContentModel.PROP_EMAIL_FEED_ID); + if ((currentMaxFeedId == null) || (currentMaxFeedId < maxFeedId)) + { + nodeService.setProperty(personNodeRef, ContentModel.PROP_EMAIL_FEED_ID, maxFeedId); + } + + userCnt.incrementAndGet(); + feedEntryCnt.addAndGet(entryCnt); + } + } + catch (InvalidNodeRefException inre) + { + // skip this person - eg. no longer exists ? + logger.warn("Skip feed notification for user ("+personNodeRef+"): " + inre.getMessage()); + } + } + }; + + // grab people for the batch processor in chunks of size batchSize + BatchProcessWorkProvider provider = new BatchProcessWorkProvider() + { + private int skip = 0; + private int maxItems = batchSize; + + @Override + public int getTotalEstimatedWorkSize() + { + return personService.countPeople(); + } + + @Override + public Collection getNextWork() + { + PagingResults people = personService.getPeople(null, true, null, new PagingRequest(skip, maxItems)); + skip += maxItems; + return people.getPage(); + } + }; + + final RetryingTransactionHelper txHelper = transactionService.getRetryingTransactionHelper(); + txHelper.setMaxRetries(0); + + new BatchProcessor( + "FeedNotifier", + txHelper, + provider, + numThreads, batchSize, + applicationContext, + logger, 100).process(worker, true); + } + catch (Throwable e) + { + // If the VM is shutting down, then ignore + if (vmShutdownListener.isVmShuttingDown()) { - return; - } - - String siteTitle = site.getTitle(); - if (siteTitle != null && siteTitle.length() > 0) - { - siteName = siteTitle; + // Ignore } else { - siteName = siteId; + logger.error("Exception during notification of feeds", e); } - - siteNames.put(siteId, siteName); } - } - - protected boolean skipUser(Boolean emailFeedDisabled, String feedUserId, String emailAddress, List excludedEmailSuffixes) - { - if ((emailFeedDisabled != null) && (emailFeedDisabled == true)) + finally { - return true; - } - - if (authenticationContext.isSystemUserName(feedUserId) || authenticationContext.isGuestUserName(feedUserId)) - { - // skip "guest" or "System" user - return true; - } - - if ((emailAddress == null) || (emailAddress.length() <= 0)) - { - // skip user that does not have an email address - if (logger.isDebugEnabled()) + int count = userCnt.get(); + int entryCount = feedEntryCnt.get(); + + // assume sends are synchronous - hence bump up to last max feed id + if (count > 0) { - logger.debug("Skip for '"+feedUserId+"' since they have no email address set"); - } - return true; - } - - String lowerEmailAddress = emailAddress.toLowerCase(); - for (String excludedEmailSuffix : excludedEmailSuffixes) - { - if (lowerEmailAddress.endsWith(excludedEmailSuffix.toLowerCase())) - { - // skip user whose email matches exclude suffix - if (logger.isDebugEnabled()) + if (logger.isInfoEnabled()) { - logger.debug("Skip for '"+feedUserId+"' since email address is excluded ("+emailAddress+")"); + // TODO i18n of info message + StringBuilder sb = new StringBuilder(); + sb.append("Notified ").append(userCnt).append(" user").append(count != 1 ? "s" : ""); + sb.append(" of ").append(feedEntryCnt).append(" activity feed entr").append(entryCount != 1 ? "ies" : "y"); + sb.append(" (in ").append(System.currentTimeMillis()-startTime).append(" msecs)"); + logger.info(sb.toString()); + } + } + else + { + if (logger.isTraceEnabled()) + { + logger.trace("Nothing to send since no new user activities found"); } - return true; } } - - return false; } - /** - * Lazily update the job lock - */ - private void refreshLock() + protected String buildSubjectText(long currentTime) { - Pair lockPair = lockThreadLocal.get(); - if (lockPair == null) + return I18NUtil.getMessage(MSG_EMAIL_SUBJECT, ModelUtil.getProductName(repoAdminService)); + } + + private String getLock(long time) + { + try { - String lockToken = jobLockService.getLock(LOCK_QNAME, LOCK_TTL); - Long lastLock = new Long(System.currentTimeMillis()); - // We have not locked before - lockPair = new Pair(lastLock, lockToken); - lockThreadLocal.set(lockPair); + return jobLockService.getLock(LOCK_QNAME, time); } - else + catch (LockAcquisitionException e) { - long now = System.currentTimeMillis(); - long lastLock = lockPair.getFirst().longValue(); - String lockToken = lockPair.getSecond(); - // Only refresh the lock if we are past a threshold - if (now - lastLock > (long)(LOCK_TTL/2L)) - { - jobLockService.refreshLock(lockToken, LOCK_QNAME, LOCK_TTL); - lastLock = System.currentTimeMillis(); - lockPair = new Pair(lastLock, lockToken); - } + return null; } } + protected void refreshLock(String lockToken, long time) + { + if (lockToken == null) + { + throw new IllegalArgumentException("Must provide existing lockToken"); + } + jobLockService.refreshLock(lockToken, LOCK_QNAME, time); + } + /** * Release the lock after the job completes */ - private void releaseLock() + private void releaseLock(String lockToken) { - Pair lockPair = lockThreadLocal.get(); - if (lockPair != null) - { - // We can't release without a token - try - { - jobLockService.releaseLock(lockPair.getSecond(), LOCK_QNAME); - } - finally - { - // Reset - lockThreadLocal.set(null); - } - } - // else: We can't release without a token + if (lockToken == null) + { + throw new IllegalArgumentException("Must provide existing lockToken"); + } + jobLockService.releaseLock(lockToken, LOCK_QNAME); } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException + { + this.applicationContext = applicationContext; + } } diff --git a/source/java/org/alfresco/repo/activities/feed/JobSettings.java b/source/java/org/alfresco/repo/activities/feed/JobSettings.java index caafc92986..0a6964fa89 100644 --- a/source/java/org/alfresco/repo/activities/feed/JobSettings.java +++ b/source/java/org/alfresco/repo/activities/feed/JobSettings.java @@ -23,7 +23,7 @@ import java.io.Serializable; /** * Job settings passed from grid task to grid job */ -public class JobSettings implements Serializable +public class JobSettings implements Serializable, Cloneable { public static final long serialVersionUID = -3896042917378679686L; @@ -81,6 +81,7 @@ public class JobSettings implements Serializable this.maxItemsPerCycle = maxItemsPerCycle; } + @Override public JobSettings clone() { JobSettings js = new JobSettings(); diff --git a/source/java/org/alfresco/repo/activities/feed/MockUserNotifier.java b/source/java/org/alfresco/repo/activities/feed/MockUserNotifier.java new file mode 100644 index 0000000000..71d473eb9a --- /dev/null +++ b/source/java/org/alfresco/repo/activities/feed/MockUserNotifier.java @@ -0,0 +1,92 @@ +package org.alfresco.repo.activities.feed; + +import java.io.Serializable; +import java.util.BitSet; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; + +/** + * A test user notifier. + * + * @since 4.0 + */ +public class MockUserNotifier extends AbstractUserNotifier +{ + /** + * Default alfresco installation url + */ + private BitSet notifiedPersonsTracker = new BitSet(); + private AtomicInteger count = new AtomicInteger(0); + + @Override + protected boolean skipUser(NodeRef personNodeRef) + { + return false; + } + + @Override + protected Long getFeedId(NodeRef personNodeRef) + { + Map personProps = nodeService.getProperties(personNodeRef); + + // where did we get up to ? + Long emailFeedDBID = (Long)personProps.get(ContentModel.PROP_EMAIL_FEED_ID); + if (emailFeedDBID != null) + { + // increment min feed id + emailFeedDBID++; + } + else + { + emailFeedDBID = -1L; + } + + return emailFeedDBID; + } + + @Override + protected void notifyUser(NodeRef personNodeRef, String subjectText, Map model, NodeRef templateNodeRef) + { + String username = (String)nodeService.getProperty(personNodeRef, ContentModel.PROP_USERNAME); + if(username.startsWith("user")) + { + int id = Integer.parseInt(username.substring(4)); + + boolean b = false; + synchronized(notifiedPersonsTracker) + { + b = notifiedPersonsTracker.get(id); + } + if(b) + { + System.out.println("Already set: " + id); + } + else + { + synchronized(notifiedPersonsTracker) + { + notifiedPersonsTracker.set(id); + } + } + } + + count.incrementAndGet(); + } + + public int countNotifications() + { + return count.get(); + } + + public int nextUserId() + { + synchronized(notifiedPersonsTracker) + { + return notifiedPersonsTracker.nextClearBit(1); + } + } +} diff --git a/source/java/org/alfresco/repo/activities/feed/RepoCtx.java b/source/java/org/alfresco/repo/activities/feed/RepoCtx.java index 8723f43eb3..2dd8dcf2e6 100644 --- a/source/java/org/alfresco/repo/activities/feed/RepoCtx.java +++ b/source/java/org/alfresco/repo/activities/feed/RepoCtx.java @@ -20,25 +20,30 @@ package org.alfresco.repo.activities.feed; import java.io.Serializable; +import org.alfresco.repo.admin.SysAdminParams; + /** * Repository context passed from grid task to grid job */ public class RepoCtx implements Serializable { - private String repoEndPoint; // http://hostname:port/webapp (eg. http://localhost:8080/alfresco) + private SysAdminParams sysAdminParams; + private String repoEndPoint; private boolean userNamesAreCaseSensitive = false; private String ticket; public static final long serialVersionUID = -3896042917378679686L; - public RepoCtx(String repoEndPoint) + public RepoCtx(SysAdminParams sysAdminParams, String repoEndPoint) { + this.sysAdminParams = sysAdminParams; this.repoEndPoint = repoEndPoint.endsWith("/") ? repoEndPoint.substring(0, repoEndPoint.length()-1) : repoEndPoint; } public String getRepoEndPoint() { - return repoEndPoint; + String base = sysAdminParams.getAlfrescoProtocol() + "://" + sysAdminParams.getAlfrescoHost() + ":" + sysAdminParams.getAlfrescoPort() + "/" + sysAdminParams.getAlfrescoContext(); + return base + repoEndPoint; } public String getTicket() diff --git a/source/java/org/alfresco/repo/activities/feed/UserNotifier.java b/source/java/org/alfresco/repo/activities/feed/UserNotifier.java new file mode 100644 index 0000000000..423d639c5f --- /dev/null +++ b/source/java/org/alfresco/repo/activities/feed/UserNotifier.java @@ -0,0 +1,17 @@ +package org.alfresco.repo.activities.feed; + +import java.util.Map; + +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.util.Pair; + +/** + * Notifies the given user by sending their activity feed information to their email address (or potentially some other destination) + * + * @since 4.0 + */ +public interface UserNotifier +{ + public Pair notifyUser(final NodeRef personNodeRef, String subjectText, Map siteNames, + String shareUrl, int repeatIntervalMins, NodeRef templateNodeRef); +} diff --git a/source/java/org/alfresco/repo/activities/feed/cleanup/FeedCleaner.java b/source/java/org/alfresco/repo/activities/feed/cleanup/FeedCleaner.java index f62802b336..4a333da1d4 100644 --- a/source/java/org/alfresco/repo/activities/feed/cleanup/FeedCleaner.java +++ b/source/java/org/alfresco/repo/activities/feed/cleanup/FeedCleaner.java @@ -136,6 +136,7 @@ public class FeedCleaner implements NodeServicePolicies.BeforeDeleteNodePolicy deleteSiteTransactionListener = new FeedCleanerDeleteSiteTransactionListener(); } + public int execute() throws JobExecutionException { checkProperties(); @@ -185,37 +186,46 @@ public class FeedCleaner implements NodeServicePolicies.BeforeDeleteNodePolicy String siteId = feed.getSiteNetwork(); final String feedUserId = feed.getFeedUserId(); String format = feed.getActivitySummaryFormat(); - + List feedToClean; int feedUserSiteCount = 0; - + long numFeeds; + if ((feedUserId == null) || (feedUserId.length() == 0)) { - feedToClean = feedDAO.selectSiteFeedEntries(siteId, format, -1); + numFeeds = feedDAO.countSiteFeedEntries(siteId, format, -1); } else { - feedToClean = feedDAO.selectUserFeedEntries(feedUserId, format, null, false, false, -1L, -1); - - if (siteService != null) - { - // note: allow for fact that Share Activities dashlet currently uses userfeed within site context - feedUserSiteCount = AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() - { - public Integer doWork() throws Exception - { - return siteService.listSites(feedUserId).size(); - } - }, AuthenticationUtil.SYSTEM_USER_NAME); - } + numFeeds = feedDAO.countUserFeedEntries(feedUserId, format, null, false, false, -1L, -1); + + if(siteService != null) + { + // note: allow for fact that Share Activities dashlet currently uses userfeed within site context + feedUserSiteCount = AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() + { + public Integer doWork() throws Exception + { + return siteService.listSites(feedUserId).size(); + } + }, AuthenticationUtil.SYSTEM_USER_NAME); + } } - - if (((feedUserSiteCount == 0) && (feedToClean.size() > maxFeedSize)) || - ((feedToClean.size() > (maxFeedSize * feedUserSiteCount)))) + + if (((feedUserSiteCount == 0) && (numFeeds > maxFeedSize)) || + ((numFeeds > (maxFeedSize * feedUserSiteCount)))) { + if ((feedUserId == null) || (feedUserId.length() == 0)) + { + feedToClean = feedDAO.selectSiteFeedEntries(siteId, format, -1); + } + else + { + feedToClean = feedDAO.selectUserFeedEntries(feedUserId, format, null, false, false, -1L, maxFeedSize); + } + Date oldestFeedEntry = feedToClean.get(maxFeedSize-1).getPostDate(); - int deletedCount = 0; if ((feedUserId == null) || (feedUserId.length() == 0)) @@ -227,7 +237,6 @@ public class FeedCleaner implements NodeServicePolicies.BeforeDeleteNodePolicy deletedCount = feedDAO.deleteUserFeedEntries(feedUserId, format, oldestFeedEntry); } - if (deletedCount > 0) { maxSizeDeletedCount = maxSizeDeletedCount + deletedCount; diff --git a/source/java/org/alfresco/repo/activities/feed/local/LocalFeedTaskSplitter.java b/source/java/org/alfresco/repo/activities/feed/local/LocalFeedTaskSplitter.java index 1cd8262b19..99d03be385 100644 --- a/source/java/org/alfresco/repo/activities/feed/local/LocalFeedTaskSplitter.java +++ b/source/java/org/alfresco/repo/activities/feed/local/LocalFeedTaskSplitter.java @@ -26,16 +26,12 @@ import org.alfresco.repo.activities.feed.FeedGridJob; import org.alfresco.repo.activities.feed.FeedTaskProcessor; import org.alfresco.repo.activities.feed.FeedTaskSplit; import org.alfresco.repo.activities.feed.JobSettings; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; /** * The local feed task splitter is responsible for splitting the feed task into feed jobs */ public class LocalFeedTaskSplitter -{ - private static final Log logger = LogFactory.getLog(LocalFeedTaskSplitter.class); - +{ private FeedTaskProcessor feedTaskProcessor; public void setFeedTaskProcessor(FeedTaskProcessor feedTaskProcessor) @@ -45,26 +41,18 @@ public class LocalFeedTaskSplitter public Collection split(int gridSize, Object o) throws Exception { - try + FeedTaskSplit feedSplitter = new FeedTaskSplit(); + Collection jobs = feedSplitter.split(gridSize, (JobSettings)o); + + List gridJobs = new ArrayList(jobs.size()); + for (JobSettings job : jobs) { - FeedTaskSplit feedSplitter = new FeedTaskSplit(); - Collection jobs = feedSplitter.split(gridSize, (JobSettings)o); - - List gridJobs = new ArrayList(jobs.size()); - for (JobSettings job : jobs) - { - LocalFeedGridJob gridJob = new LocalFeedGridJob(); - gridJob.setFeedTaskProcessor(feedTaskProcessor); - gridJob.setArgument(job); - gridJobs.add(gridJob); - } - return gridJobs; - //return (Collection)feedSplitter.split(gridSize, (JobSettings)o, new LocalFeedGridJob()); - } - catch (Exception e) - { - logger.equals(e); - throw new Exception(e.getMessage()); + LocalFeedGridJob gridJob = new LocalFeedGridJob(); + gridJob.setFeedTaskProcessor(feedTaskProcessor); + gridJob.setArgument(job); + gridJobs.add(gridJob); } + return gridJobs; + //return (Collection)feedSplitter.split(gridSize, (JobSettings)o, new LocalFeedGridJob()); } } diff --git a/source/java/org/alfresco/repo/admin/patch/impl/AVMWebProjectInheritPermissionsPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/AVMWebProjectInheritPermissionsPatch.java index 50b8e0a43b..00ce065b76 100644 --- a/source/java/org/alfresco/repo/admin/patch/impl/AVMWebProjectInheritPermissionsPatch.java +++ b/source/java/org/alfresco/repo/admin/patch/impl/AVMWebProjectInheritPermissionsPatch.java @@ -20,11 +20,9 @@ package org.alfresco.repo.admin.patch.impl; import java.util.List; -import org.springframework.extensions.surf.util.I18NUtil; import org.alfresco.model.WCMAppModel; import org.alfresco.repo.admin.patch.AbstractPatch; import org.alfresco.repo.importer.ImporterBootstrap; -import org.alfresco.repo.search.IndexerAndSearcher; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.search.ResultSet; @@ -33,6 +31,7 @@ import org.alfresco.service.cmr.search.SearchParameters; import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.namespace.RegexQNamePattern; +import org.springframework.extensions.surf.util.I18NUtil; /** * Patch to break the inheritance of permissions on AVM Web Project Folders. @@ -46,14 +45,8 @@ public class AVMWebProjectInheritPermissionsPatch extends AbstractPatch private static final String MSG_SUCCESS = "patch.avmWebProjectInheritPermissions.result"; private ImporterBootstrap spacesImporterBootstrap; - private IndexerAndSearcher indexerAndSearcher; private PermissionService permissionService; - public void setIndexerAndSearcher(IndexerAndSearcher indexerAndSearcher) - { - this.indexerAndSearcher = indexerAndSearcher; - } - public void setSpacesImporterBootstrap(ImporterBootstrap spacesImporterBootstrap) { this.spacesImporterBootstrap = spacesImporterBootstrap; diff --git a/source/java/org/alfresco/repo/admin/patch/impl/BaseReindexingPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/BaseReindexingPatch.java deleted file mode 100644 index 3b1209ae6b..0000000000 --- a/source/java/org/alfresco/repo/admin/patch/impl/BaseReindexingPatch.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * 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.admin.patch.impl; - -import org.alfresco.repo.admin.patch.AbstractPatch; -import org.alfresco.repo.search.Indexer; -import org.alfresco.repo.search.IndexerAndSearcher; -import org.alfresco.service.cmr.repository.StoreRef; -import org.alfresco.service.cmr.search.ResultSet; -import org.alfresco.service.cmr.search.ResultSetRow; -import org.alfresco.service.cmr.search.SearchParameters; -import org.alfresco.service.cmr.search.SearchService; - -/** - * Base patch for reindexing nodes in a store based on a lucene query - * - * @author Kevin Roast - * @author gavinc - */ -public abstract class BaseReindexingPatch extends AbstractPatch -{ - protected IndexerAndSearcher indexerAndSearcher; - - public void setIndexerAndSearcher(IndexerAndSearcher indexerAndSearcher) - { - this.indexerAndSearcher = indexerAndSearcher; - } - - /** - * Reindex a collection of nodes in a store based on a Lucene query - * - * @param query The Lucene query to execute to return nodes - * @param store The Store containing the nodes to reindex - */ - protected void reindex(String query, StoreRef store) - { - SearchParameters sp = new SearchParameters(); - sp.setLanguage(SearchService.LANGUAGE_LUCENE); - sp.setQuery(query); - sp.addStore(store); - ResultSet rs = null; - try - { - rs = searchService.query(sp); - for(ResultSetRow row : rs) - { - Indexer indexer = indexerAndSearcher.getIndexer(row.getNodeRef().getStoreRef()); - indexer.updateNode(row.getNodeRef()); - } - } - finally - { - if (rs != null) - { - rs.close(); - } - } - } -} diff --git a/source/java/org/alfresco/repo/admin/patch/impl/ImapUnsubscribedAspectPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/ImapUnsubscribedAspectPatch.java new file mode 100644 index 0000000000..fd18573e32 --- /dev/null +++ b/source/java/org/alfresco/repo/admin/patch/impl/ImapUnsubscribedAspectPatch.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2005-2012 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.admin.patch.impl; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.model.ImapModel; +import org.alfresco.repo.admin.patch.AbstractPatch; +import org.alfresco.repo.batch.BatchProcessWorkProvider; +import org.alfresco.repo.batch.BatchProcessor; +import org.alfresco.repo.batch.BatchProcessor.BatchProcessWorker; +import org.alfresco.repo.domain.node.NodeDAO; +import org.alfresco.repo.domain.node.NodeDAO.NodeRefQueryCallback; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.namespace.RegexQNamePattern; +import org.alfresco.util.Pair; +import org.springframework.extensions.surf.util.I18NUtil; + +public class ImapUnsubscribedAspectPatch extends AbstractPatch +{ + private static final String MSG_NONSUBSCRIBED_ASPECT_REMOVED = "patch.imapUnsubscribedAspect.result.removed"; + private static final QName ASPECT_NON_SUBSCRIBED = QName.createQName("{http://www.alfresco.org/model/imap/1.0}nonSubscribed"); + private static final String PROP_MIN_ID = "minNodeId"; + + private NodeDAO nodeDAO; + private PersonService personService; + + private final Map properties = new HashMap(); + + private int batchThreads = 3; + private int batchSize = 40; + private long count = batchThreads * batchSize; + + @Override + public void init() + { + super.init(); + properties.put(PROP_MIN_ID, 1L); + } + @Override + protected String applyInternal() throws Exception + { + final List users = nodeService.getChildAssocs(personService.getPeopleContainer(), ContentModel.ASSOC_CHILDREN, RegexQNamePattern.MATCH_ALL); + + BatchProcessWorkProvider workProvider = new BatchProcessWorkProvider() + { + final List result = new ArrayList(); + + public int getTotalEstimatedWorkSize() + { + return result.size(); + } + + public Collection getNextWork() + { + result.clear(); + nodeDAO.getNodesWithAspects(Collections.singleton(ASPECT_NON_SUBSCRIBED), properties.get(PROP_MIN_ID), count, new NodeRefQueryCallback() + { + + public boolean handle(Pair nodePair) + { + properties.put(PROP_MIN_ID, nodePair.getFirst()); + result.add(nodePair.getSecond()); + return true; + } + + }); + + return result; + } + }; + + BatchProcessor batchProcessor = new BatchProcessor("ImapUnsubscribedAspectPatch", transactionService.getRetryingTransactionHelper(), workProvider, + batchThreads, batchSize, applicationEventPublisher, null, 1000); + + BatchProcessWorker worker = new BatchProcessWorker() + { + + public void afterProcess() throws Throwable + { + } + + public void beforeProcess() throws Throwable + { + } + + public String getIdentifier(NodeRef entry) + { + return entry.toString(); + } + + public void process(NodeRef entry) throws Throwable + { + nodeService.removeAspect(entry, ImapModel.ASPECT_IMAP_FOLDER_NONSUBSCRIBED); + + for (ChildAssociationRef userRef : users) + { + nodeService.createAssociation(userRef.getChildRef(), entry, ImapModel.ASSOC_IMAP_UNSUBSCRIBED); + } + + } + + }; + batchProcessor.process(worker, true); + + return I18NUtil.getMessage(MSG_NONSUBSCRIBED_ASPECT_REMOVED); + + } + + public void setNodeDAO(NodeDAO nodeDAO) + { + this.nodeDAO = nodeDAO; + } + + public void setPersonService(PersonService personService) + { + this.personService = personService; + } + +} diff --git a/source/java/org/alfresco/repo/admin/patch/impl/MultiTShareExistingTenantsPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/MultiTShareExistingTenantsPatch.java index 4e1c28f42e..00a20ccf95 100644 --- a/source/java/org/alfresco/repo/admin/patch/impl/MultiTShareExistingTenantsPatch.java +++ b/source/java/org/alfresco/repo/admin/patch/impl/MultiTShareExistingTenantsPatch.java @@ -35,15 +35,9 @@ public class MultiTShareExistingTenantsPatch extends AbstractPatch private static final String MSG_RESULT = "patch.mtShareExistingTenants.result"; private static final String MSG_RESULT_NA = "patch.mtShareExistingTenants.result.not_applicable"; - private SiteAVMBootstrap siteBootstrap; private WorkflowDeployer workflowPatchDeployer; private List workflowDefinitions; private TenantService tenantService; - - public void setSiteAVMBootstrap(SiteAVMBootstrap siteBootstrap) - { - this.siteBootstrap = siteBootstrap; - } public void setWorkflowDeployer(WorkflowDeployer workflowPatchDeployer) { @@ -67,7 +61,6 @@ public class MultiTShareExistingTenantsPatch extends AbstractPatch protected void checkProperties() { super.checkProperties(); - checkPropertyNotNull(this.siteBootstrap, "siteAVMBootstrap"); } /** @@ -82,9 +75,7 @@ public class MultiTShareExistingTenantsPatch extends AbstractPatch } if (! tenantService.getCurrentUserDomain().equals(TenantService.DEFAULT_DOMAIN)) - { - this.siteBootstrap.bootstrap(); - + { workflowPatchDeployer.setWorkflowDefinitions(workflowDefinitions); workflowPatchDeployer.init(); } diff --git a/source/java/org/alfresco/repo/admin/patch/impl/WCMPostPermissionSnapshotPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/WCMPostPermissionSnapshotPatch.java index e3f9491caa..4dc97ee36a 100644 --- a/source/java/org/alfresco/repo/admin/patch/impl/WCMPostPermissionSnapshotPatch.java +++ b/source/java/org/alfresco/repo/admin/patch/impl/WCMPostPermissionSnapshotPatch.java @@ -37,6 +37,8 @@ import org.springframework.extensions.surf.util.I18NUtil; public class WCMPostPermissionSnapshotPatch extends AbstractPatch { private static final String MSG_SUCCESS = "patch.wcmPostPermissionSnapshotPatch.result"; + + private static final String AVM_SITE_STORE_NAME = "sitestore"; private AVMSnapShotTriggeredIndexingMethodInterceptor avmSnapShotTriggeredIndexingMethodInterceptor; @@ -71,6 +73,12 @@ public class WCMPostPermissionSnapshotPatch extends AbstractPatch List indexers = new ArrayList(stores.size()); for (AVMStoreDescriptor storeDesc : stores) { + // post 4.0 we can safely skip "sitestore" no longer used by share + if(storeDesc.getName().equals(AVM_SITE_STORE_NAME)) + { + continue; + } + AVMLuceneIndexer indexer = avmSnapShotTriggeredIndexingMethodInterceptor.getIndexer(storeDesc.getName()); indexers.add(indexer); } @@ -80,6 +88,12 @@ public class WCMPostPermissionSnapshotPatch extends AbstractPatch for (AVMStoreDescriptor storeDesc : stores) { + // post 4.0 we can safely skip "sitestore" no longer used by share + if(storeDesc.getName().equals(AVM_SITE_STORE_NAME)) + { + continue; + } + if (avmService.getStoreRoot(-1, storeDesc.getName()).getLayerID() == -1) { avmService.createSnapshot(storeDesc.getName(), "PermissionPatch", "Snapshot after 2.2 permission patch"); diff --git a/source/java/org/alfresco/repo/audit/AuditComponentImpl.java b/source/java/org/alfresco/repo/audit/AuditComponentImpl.java index 9c6d8af741..cce2cfe5bd 100644 --- a/source/java/org/alfresco/repo/audit/AuditComponentImpl.java +++ b/source/java/org/alfresco/repo/audit/AuditComponentImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2011 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -688,16 +688,20 @@ public class AuditComponentImpl implements AuditComponent Long entryId = null; if (!auditData.isEmpty()) { - // Persist the values - entryId = auditDAO.createAuditEntry(applicationId, time, username, auditData); + // Persist the values (if not just gathering data in a pre call for use in a post call) + boolean justGatherPreCallData = application.isApplicationJustGeneratingPreCallData(); + if (!justGatherPreCallData) + { + entryId = auditDAO.createAuditEntry(applicationId, time, username, auditData); + } // Done if (logger.isDebugEnabled()) { StringBuilder sb = new StringBuilder(); sb.append( - "\nNew audit entry: \n" + + ((justGatherPreCallData) ? "\nPreCallData: \n" : "\nNew audit entry: \n") + "\tApplication ID: " + applicationId + "\n" + - "\tEntry ID: " + entryId + "\n" + + ((justGatherPreCallData) ? "" : "\tEntry ID: " + entryId + "\n") + "\tValues: " + "\n"); for (Map.Entry entry : values.entrySet()) { diff --git a/source/java/org/alfresco/repo/audit/AuditComponentTest.java b/source/java/org/alfresco/repo/audit/AuditComponentTest.java index 21814d8c59..3295f1ed4d 100644 --- a/source/java/org/alfresco/repo/audit/AuditComponentTest.java +++ b/source/java/org/alfresco/repo/audit/AuditComponentTest.java @@ -36,6 +36,7 @@ import org.alfresco.repo.audit.model.AuditModelRegistryImpl; import org.alfresco.repo.security.authentication.AuthenticationException; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.security.permissions.AccessDeniedException; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.audit.AuditQueryParameters; @@ -45,6 +46,7 @@ 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.cmr.security.MutableAuthenticationService; +import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.ApplicationContextHelper; import org.alfresco.util.EqualsHelper; @@ -52,6 +54,7 @@ import org.apache.commons.lang.mutable.MutableInt; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.context.ApplicationContext; +import org.springframework.extensions.webscripts.GUID; import org.springframework.util.ResourceUtils; /** @@ -68,6 +71,7 @@ public class AuditComponentTest extends TestCase private static final String APPLICATION_TEST = "Alfresco Test"; private static final String APPLICATION_ACTIONS_TEST = "Actions Test"; private static final String APPLICATION_API_TEST = "Test AuthenticationService"; + private static final String APPLICATION_ALF12638_TEST = "Test ALF-12638"; private static final Log logger = LogFactory.getLog(AuditComponentTest.class); @@ -696,6 +700,89 @@ public class AuditComponentTest extends TestCase params.setToId(Long.MAX_VALUE); queryAuditLog(auditQueryCallback, params, 1); } + + /** + * See ALF-12638 + */ + public void testAuditFailedNodeAccess() throws Exception + { + AuditQueryParameters params = new AuditQueryParameters(); + params.setForward(true); + params.setApplicationName(APPLICATION_ALF12638_TEST); + + // Load in the config for this specific test: alfresco-audit-test-authenticationservice.xml + URL testModelUrl = ResourceUtils.getURL("classpath:alfresco/testaudit/alfresco-audit-test-alf-12638.xml"); + auditModelRegistry.registerModel(testModelUrl); + auditModelRegistry.loadAuditModels(); + + // There should be a log entry for the application + final List results = new ArrayList(5); + final StringBuilder sb = new StringBuilder(); + AuditQueryCallback auditQueryCallback = new AuditQueryCallback() + { + public boolean valuesRequired() + { + return true; + } + + public boolean handleAuditEntry( + Long entryId, + String applicationName, + String user, + long time, + Map values) + { + results.add(entryId); + sb.append("Row: ") + .append(entryId).append(" | ") + .append(applicationName).append(" | ") + .append(user).append(" | ") + .append(new Date(time)).append(" | ") + .append(values).append(" | ") + .append("\n"); + ; + return true; + } + + public boolean handleAuditEntryError(Long entryId, String errorMsg, Throwable error) + { + throw new AlfrescoRuntimeException(errorMsg, error); + } + }; + + clearAuditLog(APPLICATION_ALF12638_TEST); + results.clear(); + sb.delete(0, sb.length()); + queryAuditLog(auditQueryCallback, params, -1); + assertTrue("There should be no audit entries for the API test after a clear", results.isEmpty()); + + try + { + nodeService.getRootNode(new StoreRef("system://system")); + fail("Should not be allowed to get 'system://system'"); + } + catch (AccessDeniedException e) + { + // Expected + } + // Try this for a while until we get a result + boolean success = false; + for (int i = 0; i < 30; i++) + { + queryAuditLog(auditQueryCallback, params, -1); + if (results.size() > 1) + { + logger.debug(sb.toString()); + success = true; + break; + } + synchronized(this) + { + try { this.wait(1000L); } catch (InterruptedException e) {} + } + } + assertTrue("There should be exactly one audit entry for the API test", success); + } /** * Clearn the audit log as 'admin' diff --git a/source/java/org/alfresco/repo/audit/AuditMethodInterceptor.java b/source/java/org/alfresco/repo/audit/AuditMethodInterceptor.java index b0f1ffda89..b5c27bbb37 100644 --- a/source/java/org/alfresco/repo/audit/AuditMethodInterceptor.java +++ b/source/java/org/alfresco/repo/audit/AuditMethodInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2011 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -21,12 +21,16 @@ package org.alfresco.repo.audit; import java.io.Serializable; import java.util.HashMap; import java.util.Map; +import java.util.Map.Entry; import java.util.concurrent.ThreadPoolExecutor; +import net.sf.acegisecurity.Authentication; + import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.error.StackTraceUtil; import org.alfresco.repo.audit.model.AuditApplication; import org.alfresco.repo.domain.schema.SchemaBootstrap; +import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.Auditable; import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; @@ -84,6 +88,7 @@ public class AuditMethodInterceptor implements MethodInterceptor public static final String AUDIT_PATH_API_PRE = AUDIT_PATH_API_ROOT + "/pre"; public static final String AUDIT_PATH_API_POST = AUDIT_PATH_API_ROOT + "/post"; public static final String AUDIT_SNIPPET_ARGS = "/args"; + public static final String AUDIT_SNIPPET_PRE_CALL_DATA = "/preCallData"; public static final String AUDIT_SNIPPET_RESULT = "/result"; public static final String AUDIT_SNIPPET_ERROR = "/error"; public static final String AUDIT_SNIPPET_NO_ERROR = "/no-error"; @@ -211,9 +216,10 @@ public class AuditMethodInterceptor implements MethodInterceptor String methodName, Map namedArguments) throws Throwable { + Map preAuditedData = null; try { - auditInvocationBefore(serviceName, methodName, namedArguments); + preAuditedData = auditInvocationBefore(serviceName, methodName, namedArguments); } catch (Throwable e) { @@ -240,7 +246,7 @@ public class AuditMethodInterceptor implements MethodInterceptor Object auditRet = auditableDef.recordReturnedObject() ? ret : null; try { - auditInvocationAfter(serviceName, methodName, namedArguments, auditRet, thrown); + auditInvocationAfter(serviceName, methodName, namedArguments, auditRet, thrown, preAuditedData); } catch (Throwable e) { @@ -334,7 +340,7 @@ public class AuditMethodInterceptor implements MethodInterceptor * * @since 3.2 */ - private void auditInvocationBefore( + private Map auditInvocationBefore( final String serviceName, final String methodName, final Map namedArguments) @@ -362,6 +368,7 @@ public class AuditMethodInterceptor implements MethodInterceptor "Audited before invocation: \n" + " Values: " + auditedData); } + return auditedData; } /** @@ -372,12 +379,13 @@ public class AuditMethodInterceptor implements MethodInterceptor * @param namedArguments the named arguments passed to the invocation * @param ret the result of the execution (may be null) * @param thrown the error thrown by the invocation (may be null) + * @param preAuditedData the audited data from before the method call. * * @since 3.2 */ private void auditInvocationAfter( String serviceName, String methodName, Map namedArguments, - Object ret, final Throwable thrown) + Object ret, final Throwable thrown, Map preAuditedData) { final String rootPath = AuditApplication.buildPath(AUDIT_PATH_API_POST, serviceName, methodName); @@ -397,6 +405,14 @@ public class AuditMethodInterceptor implements MethodInterceptor argValue); } } + + // Add audited data prior to the method call, so it may be repeated + for (Entry entry: preAuditedData.entrySet()) + { + String path = AuditApplication.buildPath(AUDIT_SNIPPET_PRE_CALL_DATA, entry.getKey()); + auditData.put(path, entry.getValue()); + } + if (ret != null) { if (ret instanceof String) @@ -426,7 +442,9 @@ public class AuditMethodInterceptor implements MethodInterceptor if (thrown != null) { // ALF-3055: an exception has occurred - make sure the audit occurs in a new thread - // rather than a nested transaction to avoid contention for the same audit table + // rather than a nested transaction to avoid contention for the same audit table + // ALF-12638: ensure that the new thread context matches the current thread context + final Authentication authContext = AuthenticationUtil.getFullAuthentication(); threadPoolExecutor.execute(new Runnable() { public void run() @@ -438,7 +456,7 @@ public class AuditMethodInterceptor implements MethodInterceptor thrown.getMessage(), thrown.getStackTrace(), sb, Integer.MAX_VALUE); auditData.put(AUDIT_SNIPPET_ERROR, SchemaBootstrap.trimStringForTextFields(sb.toString())); - // An exception will generally roll the current transaction back + // Ensure we have a transaction RetryingTransactionCallback> auditCallback = new RetryingTransactionCallback>() { @@ -447,7 +465,15 @@ public class AuditMethodInterceptor implements MethodInterceptor return auditComponent.recordAuditValues(rootPath, auditData); } }; - auditedData = transactionService.getRetryingTransactionHelper().doInTransaction(auditCallback, false, true); + try + { + AuthenticationUtil.setFullAuthentication(authContext); + auditedData = transactionService.getRetryingTransactionHelper().doInTransaction(auditCallback, false, true); + } + finally + { + AuthenticationUtil.clearCurrentSecurityContext(); + } // Done if (logger.isDebugEnabled() && auditedData.size() > 0) diff --git a/source/java/org/alfresco/repo/audit/model/AuditApplication.java b/source/java/org/alfresco/repo/audit/model/AuditApplication.java index 8604dbe58e..eda8e7e17a 100644 --- a/source/java/org/alfresco/repo/audit/model/AuditApplication.java +++ b/source/java/org/alfresco/repo/audit/model/AuditApplication.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -45,6 +45,7 @@ import org.apache.commons.logging.LogFactory; */ public class AuditApplication { + public static final String AUDIT_APPLICATION_PREFIX_FOR_PRE_DATA = "PreCallData"; public static final String AUDIT_PATH_SEPARATOR = "/"; public static final String AUDIT_KEY_REGEX = "[a-zA-Z0-9\\-\\_\\.]+"; public static final String AUDIT_PATH_REGEX = "(/[a-zA-Z0-9:\\-\\_\\.]+)+"; @@ -501,4 +502,18 @@ public class AuditApplication " Application: " + applicationName + "\n" + " Path: " + path); } + + /** + * Returns {@code true} if the application name has a prefix of {@code "PreCallData"} + * that indicates that the only purpose of the Application is to generate data to be + * passed to a post call audit application. In this situation the application's + * audit data is not audited. This allows the post audit application to have access to + * 'before' values including those created by extractors and generators. Some of which + * will not be available (for example the node has been deleted) or will have changed + * as a result of the call. + */ + public boolean isApplicationJustGeneratingPreCallData() + { + return applicationName != null && applicationName.startsWith(AUDIT_APPLICATION_PREFIX_FOR_PRE_DATA); + } } diff --git a/source/java/org/alfresco/repo/avm/AVMLockingAwareService.java b/source/java/org/alfresco/repo/avm/AVMLockingAwareService.java index ade71a363b..a9bb46e692 100644 --- a/source/java/org/alfresco/repo/avm/AVMLockingAwareService.java +++ b/source/java/org/alfresco/repo/avm/AVMLockingAwareService.java @@ -48,6 +48,7 @@ import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.namespace.QName; import org.alfresco.util.Pair; import org.alfresco.wcm.util.WCMUtil; +import org.alfresco.wcm.webproject.WebProjectService; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; @@ -71,6 +72,8 @@ public class AVMLockingAwareService implements AVMService, ApplicationContextAwa private ApplicationContext fContext; + private WebProjectService wpService; + public AVMLockingAwareService() { } @@ -85,6 +88,7 @@ public class AVMLockingAwareService implements AVMService, ApplicationContextAwa fService = (AVMService)fContext.getBean("avmService"); fLockingService = (AVMLockingService)fContext.getBean("avmLockingService"); permissionService = (PermissionService) fContext.getBean("PermissionService"); + wpService = (WebProjectService) fContext.getBean("WebProjectService"); } public void addAspect(String path, QName aspectName) @@ -664,6 +668,10 @@ public class AVMLockingAwareService implements AVMService, ApplicationContextAwa { String userName = AuthenticationUtil.getFullyAuthenticatedUser(); LockState lockState = fLockingService.getLockState(webProject, storePath[1], userName); + // Managers can edit any file in any sandbox, look into ALF-11440 + String wpStoreId = WCMUtil.getWebProjectStoreId(webProject); + if (lockState == AVMLockingService.LockState.LOCK_NOT_OWNER && wpService.isContentManager(wpStoreId, userName)) + lockState = AVMLockingService.LockState.LOCK_OWNER; switch (lockState) { case LOCK_NOT_OWNER: diff --git a/source/java/org/alfresco/repo/avm/AVMNodeService.java b/source/java/org/alfresco/repo/avm/AVMNodeService.java index c31c3f85be..133c01a9b7 100644 --- a/source/java/org/alfresco/repo/avm/AVMNodeService.java +++ b/source/java/org/alfresco/repo/avm/AVMNodeService.java @@ -2032,5 +2032,11 @@ public class AVMNodeService extends AbstractNodeServiceImpl implements NodeServi { throw new UnsupportedOperationException("AVM does not support this operation."); } + + @Override + public int countChildAssocs(NodeRef nodeRef, boolean isPrimary) throws InvalidNodeRefException + { + throw new UnsupportedOperationException("AVM does not support this operation."); + } } diff --git a/source/java/org/alfresco/repo/config/xml/RepoXMLConfigService.java b/source/java/org/alfresco/repo/config/xml/RepoXMLConfigService.java index e14a0f3c59..64cfa1ad6c 100644 --- a/source/java/org/alfresco/repo/config/xml/RepoXMLConfigService.java +++ b/source/java/org/alfresco/repo/config/xml/RepoXMLConfigService.java @@ -433,7 +433,7 @@ public class RepoXMLConfigService extends XMLConfigService implements TenantDepl return tenantAdminService.getCurrentUserDomain(); } - private class ConfigData + private static class ConfigData { private ConfigImpl globalConfig; private Map evaluators; diff --git a/source/java/org/alfresco/repo/content/ContentStore.java b/source/java/org/alfresco/repo/content/ContentStore.java index b1d9a3338f..7a521f6550 100644 --- a/source/java/org/alfresco/repo/content/ContentStore.java +++ b/source/java/org/alfresco/repo/content/ContentStore.java @@ -117,6 +117,7 @@ public interface ContentStore * if no size data is available. * * @since 3.3.3 + * @deprecated This method takes too long to complete in many situations (see ALF-12410). */ public long getSpaceUsed(); diff --git a/source/java/org/alfresco/repo/content/caching/cleanup/CachedContentCleanupJobTest.java b/source/java/org/alfresco/repo/content/caching/cleanup/CachedContentCleanupJobTest.java index 12b0d229a9..433dc990dd 100644 --- a/source/java/org/alfresco/repo/content/caching/cleanup/CachedContentCleanupJobTest.java +++ b/source/java/org/alfresco/repo/content/caching/cleanup/CachedContentCleanupJobTest.java @@ -232,7 +232,7 @@ public class CachedContentCleanupJobTest for (int i = 0; i < numFiles; i++) { - Calendar calendar = new GregorianCalendar(2010, 12, 2, 17, i); + Calendar calendar = new GregorianCalendar(2010, 11, 2, 17, i); if (i >= 21 && i <= 24) { diff --git a/source/java/org/alfresco/repo/content/cleanup/ContentStoreCleaner.java b/source/java/org/alfresco/repo/content/cleanup/ContentStoreCleaner.java index cc11acb9bd..34af0946a1 100644 --- a/source/java/org/alfresco/repo/content/cleanup/ContentStoreCleaner.java +++ b/source/java/org/alfresco/repo/content/cleanup/ContentStoreCleaner.java @@ -333,7 +333,7 @@ public class ContentStoreCleaner private void executeInternal() { - final long maxOrphanTime = System.currentTimeMillis() - (protectDays * 24 * 3600 * 1000); + final long maxOrphanTime = System.currentTimeMillis() - (protectDays * 24 * 3600 * 1000L); // execute in READ-WRITE txn RetryingTransactionCallback getAndDeleteWork = new RetryingTransactionCallback() { diff --git a/source/java/org/alfresco/repo/content/filestore/FileContentStore.java b/source/java/org/alfresco/repo/content/filestore/FileContentStore.java index f816d17219..f9e2cef0e2 100644 --- a/source/java/org/alfresco/repo/content/filestore/FileContentStore.java +++ b/source/java/org/alfresco/repo/content/filestore/FileContentStore.java @@ -361,36 +361,6 @@ public class FileContentStore return file.exists(); } - /** - * Recursive directory size calculation - */ - private long calculateDirectorySize(File dir) - { - int size = 0; - File[] files = dir.listFiles(); - for (File file : files) - { - if (file.isDirectory()) - { - size += calculateDirectorySize(file); - } - else - { - size += file.length(); - } - } - return size; - } - - /** - * Performs a full, deep size calculation - */ - @Override - public long getSpaceUsed() - { - return calculateDirectorySize(rootDirectory); - } - /** * Get the filesystem's free space. * diff --git a/source/java/org/alfresco/repo/content/filestore/FileContentStoreTest.java b/source/java/org/alfresco/repo/content/filestore/FileContentStoreTest.java index 1ad22c7bde..ecda2aeabd 100644 --- a/source/java/org/alfresco/repo/content/filestore/FileContentStoreTest.java +++ b/source/java/org/alfresco/repo/content/filestore/FileContentStoreTest.java @@ -94,18 +94,6 @@ public class FileContentStoreTest extends AbstractWritableContentStoreTest File dir = new File(root); assertTrue("Root location for FileContentStore must exist", dir.exists()); } - - /** - * Ensures that the size is positive - */ - @Override - public void testSpaceUsed() throws Exception - { - ContentStore store = getStore(); - store.getWriter(new ContentContext(null, null)).putContent("Test content"); - long size = store.getSpaceUsed(); - assertTrue("Size must be positive", size > 0L); - } /** * Ensures that the size is something other than -1 or Long.MAX_VALUE diff --git a/source/java/org/alfresco/repo/content/metadata/TikaPoweredMetadataExtracter.java b/source/java/org/alfresco/repo/content/metadata/TikaPoweredMetadataExtracter.java index 8b20c67c29..122ae6af3e 100644 --- a/source/java/org/alfresco/repo/content/metadata/TikaPoweredMetadataExtracter.java +++ b/source/java/org/alfresco/repo/content/metadata/TikaPoweredMetadataExtracter.java @@ -18,7 +18,6 @@ */ package org.alfresco.repo.content.metadata; -import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; @@ -33,11 +32,12 @@ import java.util.Locale; import java.util.Map; import java.util.TimeZone; +import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.content.filestore.FileContentReader; import org.alfresco.service.cmr.repository.ContentReader; -import org.alfresco.util.TempFileProvider; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.tika.io.TemporaryResources; import org.apache.tika.io.TikaInputStream; import org.apache.tika.metadata.Metadata; import org.apache.tika.mime.MediaType; @@ -216,21 +216,29 @@ public abstract class TikaPoweredMetadataExtracter extends AbstractMappingMetada * already there */ private InputStream getInputStream(ContentReader reader) throws IOException { - if("image/jpeg".equals(reader.getMimetype()) || - "image/tiff".equals(reader.getMimetype())) + // Prefer the File if available, it's generally quicker + if(reader instanceof FileContentReader) { - if(reader instanceof FileContentReader) - { - return TikaInputStream.get( ((FileContentReader)reader).getFile() ); - } - else - { - File tmpFile = TempFileProvider.createTempFile("tika", "tmp"); - reader.getContent(tmpFile); - return TikaInputStream.get(tmpFile); - } + return TikaInputStream.get( ((FileContentReader)reader).getFile() ); + } + + // Grab the InputStream for the Content + InputStream input = reader.getContentInputStream(); + + // Images currently always require a file + if(MimetypeMap.MIMETYPE_IMAGE_JPEG.equals(reader.getMimetype()) || + MimetypeMap.MIMETYPE_IMAGE_TIFF.equals(reader.getMimetype())) + { + TemporaryResources tmp = new TemporaryResources(); + TikaInputStream stream = TikaInputStream.get(input, tmp); + stream.getFile(); // Have it turned into File backed + return stream; + } + else + { + // The regular Content InputStream should be fine + return input; } - return reader.getContentInputStream(); } @Override diff --git a/source/java/org/alfresco/repo/content/replication/ContentStoreReplicatorTest.java b/source/java/org/alfresco/repo/content/replication/ContentStoreReplicatorTest.java index 2ad636cac5..9a07e4d8c4 100644 --- a/source/java/org/alfresco/repo/content/replication/ContentStoreReplicatorTest.java +++ b/source/java/org/alfresco/repo/content/replication/ContentStoreReplicatorTest.java @@ -179,8 +179,8 @@ public class ContentStoreReplicatorTest extends TestCase sourceStore.getUrls(sourceUrls); targetStore.getUrls(targetUrls); - sourceUrls.urls.containsAll(targetUrls.urls); - targetUrls.urls.contains(sourceUrls.urls); + assertTrue("Source must be equal to target", sourceUrls.urls.containsAll(targetUrls.urls)); + //targetUrls.urls.contains(sourceUrls.urls); } /** diff --git a/source/java/org/alfresco/repo/content/transform/AbstractContentTransformer2.java b/source/java/org/alfresco/repo/content/transform/AbstractContentTransformer2.java index 41e4226ff3..2748c44ef5 100644 --- a/source/java/org/alfresco/repo/content/transform/AbstractContentTransformer2.java +++ b/source/java/org/alfresco/repo/content/transform/AbstractContentTransformer2.java @@ -23,6 +23,7 @@ import java.util.Map; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.service.cmr.repository.ContentIOException; import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentServiceTransientException; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.TransformationOptions; import org.apache.commons.logging.Log; @@ -170,6 +171,23 @@ public abstract class AbstractContentTransformer2 extends AbstractContentTransfo // Transform transformInternal(reader, writer, options); } + catch (ContentServiceTransientException cste) + { + // A transient failure has occurred within the content transformer. + // This should not be interpreted as a failure and therefore we should not + // update the transformer's average time. + if (logger.isDebugEnabled()) + { + logger.debug("Transformation has been transiently declined: \n" + + " reader: " + reader + "\n" + + " writer: " + writer + "\n" + + " options: " + options + "\n" + + " transformer: " + this); + } + // the finally block below will still perform tidyup. Otherwise we're done. + // We rethrow the exception + throw cste; + } catch (Throwable e) { // Make sure that this transformation gets set back i.t.o. time taken. diff --git a/source/java/org/alfresco/repo/content/transform/AbstractContentTransformerTest.java b/source/java/org/alfresco/repo/content/transform/AbstractContentTransformerTest.java index 1e66141149..f94cce39c5 100644 --- a/source/java/org/alfresco/repo/content/transform/AbstractContentTransformerTest.java +++ b/source/java/org/alfresco/repo/content/transform/AbstractContentTransformerTest.java @@ -24,6 +24,7 @@ import java.io.IOException; import java.io.OutputStreamWriter; import java.net.URL; import java.nio.charset.Charset; +import java.util.Arrays; import java.util.Date; import java.util.HashSet; import java.util.Set; diff --git a/source/java/org/alfresco/repo/content/transform/AppleIWorksContentTransformer.java b/source/java/org/alfresco/repo/content/transform/AppleIWorksContentTransformer.java index 758248a37b..779c987855 100644 --- a/source/java/org/alfresco/repo/content/transform/AppleIWorksContentTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/AppleIWorksContentTransformer.java @@ -18,26 +18,20 @@ */ package org.alfresco.repo.content.transform; -import java.io.BufferedOutputStream; -import java.io.File; import java.io.FileNotFoundException; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; import java.util.Arrays; import java.util.List; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.TransformationOptions; -import org.alfresco.util.TempFileProvider; +import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; +import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.apache.log4j.lf5.util.StreamUtils; /** * Converts Apple iWorks files to PDFs or JPEGs for thumbnailing & previewing. @@ -89,31 +83,35 @@ public class AppleIWorksContentTransformer extends AbstractContentTransformer2 } - ZipFile iworksZipfile = null; + ZipArchiveInputStream iWorksZip = null; try { - File iWorksTempFile = TempFileProvider.createTempFile(this.getClass().getSimpleName() + "_iWorks", sourceExtension); - reader.getContent(iWorksTempFile); - // iWorks files are zip files (at least in recent versions, iWork 09). // If it's not a zip file, the resultant ZipException will be caught as an IOException below. - iworksZipfile = new ZipFile(iWorksTempFile); + iWorksZip = new ZipArchiveInputStream(reader.getContentInputStream()); - File tempOutFile = null; - if (MimetypeMap.MIMETYPE_IMAGE_JPEG.equals(targetMimetype)) + ZipArchiveEntry entry = null; + boolean found = false; + while ( !found && (entry = iWorksZip.getNextZipEntry()) != null ) { - tempOutFile = copyZipEntryToTempFile(QUICK_LOOK_THUMBNAIL_JPG, iworksZipfile); + if (MimetypeMap.MIMETYPE_IMAGE_JPEG.equals(targetMimetype) && + entry.getName().equals(QUICK_LOOK_THUMBNAIL_JPG)) + { + writer.putContent( iWorksZip ); + found = true; + } + else if (MimetypeMap.MIMETYPE_PDF.equals(targetMimetype) && + entry.getName().equals(QUICK_LOOK_PREVIEW_PDF)) + { + writer.putContent( iWorksZip ); + found = true; + } } - else if (MimetypeMap.MIMETYPE_PDF.equals(targetMimetype)) - { - tempOutFile = copyZipEntryToTempFile(QUICK_LOOK_PREVIEW_PDF, iworksZipfile); - } - else + + if (! found) { throw new AlfrescoRuntimeException("Unable to transform " + sourceExtension + " file to " + targetMimetype); } - - writer.putContent(tempOutFile); } catch (FileNotFoundException e1) { @@ -125,48 +123,10 @@ public class AppleIWorksContentTransformer extends AbstractContentTransformer2 } finally { - if (iworksZipfile != null) + if (iWorksZip != null) { - iworksZipfile.close(); + iWorksZip.close(); } } } - - /** - * This method copies the contents of the specified zip-entry in the specified zip file - * to a temporary file. - * - * @return the File object for the just-created temporary file. - */ - private File copyZipEntryToTempFile(String zipEntryName, ZipFile iworksZipfile) throws IOException - { - final String extension = zipEntryName.endsWith(".jpg") ? ".jpg" : ".pdf"; - ZipEntry embeddedQuicklookResource = iworksZipfile.getEntry(zipEntryName); - - if (embeddedQuicklookResource == null) - { - throw new AlfrescoRuntimeException("Unable to transform iWorks file as there was no embedded preview."); - } - - File outputFile = TempFileProvider.createTempFile(this.getClass().getSimpleName() + "_ZipEntry", extension); - - BufferedOutputStream bufOut = null; - - try - { - InputStream zin = iworksZipfile.getInputStream(embeddedQuicklookResource); - bufOut = new BufferedOutputStream(new FileOutputStream(outputFile)); - StreamUtils.copy(zin, bufOut); - } - finally - { - // zin closed in calling method - if (bufOut != null) - { - bufOut.close(); - } - } - - return outputFile; - } } diff --git a/source/java/org/alfresco/repo/content/transform/HtmlParserContentTransformer.java b/source/java/org/alfresco/repo/content/transform/HtmlParserContentTransformer.java index 09f90d0abe..deeda64c1e 100644 --- a/source/java/org/alfresco/repo/content/transform/HtmlParserContentTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/HtmlParserContentTransformer.java @@ -19,6 +19,7 @@ package org.alfresco.repo.content.transform; import java.io.File; +import java.net.URLConnection; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.service.cmr.repository.ContentReader; @@ -27,7 +28,9 @@ import org.alfresco.service.cmr.repository.TransformationOptions; import org.alfresco.util.TempFileProvider; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.htmlparser.Parser; import org.htmlparser.beans.StringBean; +import org.htmlparser.util.ParserException; /** * @see http://htmlparser.sourceforge.net/ @@ -64,20 +67,82 @@ public class HtmlParserContentTransformer extends AbstractContentTransformer2 public void transformInternal(ContentReader reader, ContentWriter writer, TransformationOptions options) throws Exception { - // we can only work from a file + // We can only work from a file File htmlFile = TempFileProvider.createTempFile("HtmlParserContentTransformer_", ".html"); reader.getContent(htmlFile); - // create the extractor - StringBean extractor = new StringBean(); + // Fetch the encoding of the HTML, if it's set in Alfresco + String encoding = reader.getEncoding(); + + // Create the extractor + EncodingAwareStringBean extractor = new EncodingAwareStringBean(); extractor.setCollapse(false); extractor.setLinks(false); extractor.setReplaceNonBreakingSpaces(false); - extractor.setURL(htmlFile.getAbsolutePath()); - + extractor.setURL(htmlFile, encoding); // get the text String text = extractor.getStrings(); // write it to the writer writer.putContent(text); + + // Tidy up + htmlFile.delete(); + } + + /** + * A version of {@link StringBean} which allows control of the + * encoding in the underlying HTML Parser. + * Unfortunately, StringBean doesn't allow easy over-riding of + * this, so we have to duplicate some code to control this. + * This allows us to correctly handle HTML files where the encoding + * is specified against the content property (rather than in the + * HTML Head Meta), see ALF-10466 for details. + */ + class EncodingAwareStringBean extends StringBean + { + private static final long serialVersionUID = -9033414360428669553L; + + /** + * Sets the File to extract strings from, and the encoding + * it's in (if known to Alfresco) + * + * @param file The File that text should be fetched from. + * @param encoding The encoding of the input + */ + public void setURL(File file, String encoding) + { + String previousURL = getURL(); + String newURL = file.getAbsolutePath(); + + if ( (previousURL == null) || (!newURL.equals(previousURL)) ) + { + try + { + URLConnection conn = getConnection(); + + if (null == mParser) + { + mParser = new Parser(newURL); + } + else + { + mParser.setURL(newURL); + } + + if (encoding != null) + { + mParser.setEncoding(encoding); + } + + mPropertySupport.firePropertyChange(PROP_URL_PROPERTY, previousURL, getURL()); + mPropertySupport.firePropertyChange(PROP_CONNECTION_PROPERTY, conn, mParser.getConnection()); + setStrings(); + } + catch (ParserException pe) + { + updateStrings(pe.toString()); + } + } + } } } diff --git a/source/java/org/alfresco/repo/content/transform/HtmlParserContentTransformerTest.java b/source/java/org/alfresco/repo/content/transform/HtmlParserContentTransformerTest.java index feadb5e3fd..9c776d95da 100644 --- a/source/java/org/alfresco/repo/content/transform/HtmlParserContentTransformerTest.java +++ b/source/java/org/alfresco/repo/content/transform/HtmlParserContentTransformerTest.java @@ -18,7 +18,12 @@ */ package org.alfresco.repo.content.transform; +import java.io.ByteArrayInputStream; +import java.io.File; + import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.content.filestore.FileContentWriter; +import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.TransformationOptions; /** @@ -59,4 +64,142 @@ public class HtmlParserContentTransformerTest extends AbstractContentTransformer reliability = transformer.isTransformable(MimetypeMap.MIMETYPE_TEXT_PLAIN, -1, MimetypeMap.MIMETYPE_HTML, new TransformationOptions()); assertFalse(reliability); // plain text to plain text is not supported } + + /** + * Checks that we correctly handle text in different encodings, + * no matter if the encoding is specified on the Content Property + * or in a meta tag within the HTML itself. (ALF-10466) + */ + public void testEncodingHandling() throws Exception + { + final String TITLE = "Testing!"; + final String TEXT_P1 = "This is some text in English"; + final String TEXT_P2 = "This is more text in English"; + final String TEXT_P3 = "C'est en Fran\u00e7ais et Espa\u00f1ol"; + String partA = ""+TITLE+""; + String partB = "\n

"+TEXT_P1+"

\n" + + "

"+TEXT_P2+"

\n" + "

"+TEXT_P3+"

\n"; + String partC = ""; + + ContentWriter content; + ContentWriter dest; + File tmpS = null; + File tmpD = null; + + try + { + // Content set to ISO 8859-1 + tmpS = File.createTempFile("test", ".html"); + content = new FileContentWriter(tmpS); + content.setEncoding("ISO-8859-1"); + content.setMimetype(MimetypeMap.MIMETYPE_HTML); + content.putContent(partA+partB+partC); + + tmpD = File.createTempFile("test", ".txt"); + dest = new FileContentWriter(tmpD); + dest.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN); + + transformer.transform(content.getReader(), dest); + assertEquals( + TITLE + "\n" + TEXT_P1 + "\n" + TEXT_P2 + "\n" + TEXT_P3 + "\n", + dest.getReader().getContentString() + ); + tmpS.delete(); + tmpD.delete(); + + + // Content set to UTF-8 + tmpS = File.createTempFile("test", ".html"); + content = new FileContentWriter(tmpS); + content.setEncoding("UTF-8"); + content.setMimetype(MimetypeMap.MIMETYPE_HTML); + content.putContent(partA+partB+partC); + + tmpD = File.createTempFile("test", ".txt"); + dest = new FileContentWriter(tmpD); + dest.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN); + + transformer.transform(content.getReader(), dest); + assertEquals( + TITLE + "\n" + TEXT_P1 + "\n" + TEXT_P2 + "\n" + TEXT_P3 + "\n", + dest.getReader().getContentString() + ); + tmpS.delete(); + tmpD.delete(); + + + // Content set to UTF-16 + tmpS = File.createTempFile("test", ".html"); + content = new FileContentWriter(tmpS); + content.setEncoding("UTF-16"); + content.setMimetype(MimetypeMap.MIMETYPE_HTML); + content.putContent(partA+partB+partC); + + tmpD = File.createTempFile("test", ".txt"); + dest = new FileContentWriter(tmpD); + dest.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN); + + transformer.transform(content.getReader(), dest); + assertEquals( + TITLE + "\n" + TEXT_P1 + "\n" + TEXT_P2 + "\n" + TEXT_P3 + "\n", + dest.getReader().getContentString() + ); + tmpS.delete(); + tmpD.delete(); + + + // Nothing on the content, meta set to ISO 8865-1 + tmpS = File.createTempFile("test", ".html"); + content = new FileContentWriter(tmpS); + content.setMimetype(MimetypeMap.MIMETYPE_HTML); + String str = partA+ + "" + + partB+partC; + content.putContent(new ByteArrayInputStream(str.getBytes("ISO-8859-1"))); + + tmpD = File.createTempFile("test", ".txt"); + dest = new FileContentWriter(tmpD); + dest.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN); + + transformer.transform(content.getReader(), dest); + assertEquals( + TITLE + "\n" + TEXT_P1 + "\n" + TEXT_P2 + "\n" + TEXT_P3 + "\n", + dest.getReader().getContentString() + ); + tmpS.delete(); + tmpD.delete(); + + + // Nothing on the content, meta set to UTF-8 + tmpS = File.createTempFile("test", ".html"); + content = new FileContentWriter(tmpS); + content.setMimetype(MimetypeMap.MIMETYPE_HTML); + str = partA+ + "" + + partB+partC; + content.putContent(new ByteArrayInputStream(str.getBytes("UTF-8"))); + + tmpD = File.createTempFile("test", ".txt"); + dest = new FileContentWriter(tmpD); + dest.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN); + + transformer.transform(content.getReader(), dest); + assertEquals( + TITLE + "\n" + TEXT_P1 + "\n" + TEXT_P2 + "\n" + TEXT_P3 + "\n", + dest.getReader().getContentString() + ); + tmpS.delete(); + tmpD.delete(); + + + // Note - we can't test UTF-16 with only a meta encoding, + // because without that the parser won't know about the + // 2 byte format so won't be able to identify the meta tag + } + finally + { + if (tmpS != null && tmpS.exists()) tmpS.delete(); + if (tmpD != null && tmpD.exists()) tmpD.delete(); + } + } } diff --git a/source/java/org/alfresco/repo/content/transform/OOXMLThumbnailContentTransformer.java b/source/java/org/alfresco/repo/content/transform/OOXMLThumbnailContentTransformer.java new file mode 100644 index 0000000000..c882bd24dd --- /dev/null +++ b/source/java/org/alfresco/repo/content/transform/OOXMLThumbnailContentTransformer.java @@ -0,0 +1,125 @@ +/* + * 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.content.transform; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.List; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.TransformationOptions; +import org.alfresco.util.TempFileProvider; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.poi.openxml4j.opc.OPCPackage; +import org.apache.poi.openxml4j.opc.PackagePart; +import org.apache.poi.openxml4j.opc.PackageRelationship; +import org.apache.poi.openxml4j.opc.PackageRelationshipCollection; +import org.apache.poi.openxml4j.opc.PackageRelationshipTypes; + +/** + * Extracts out Thumbnail JPEGs from OOXML files for thumbnailing & previewing. + * This transformer will only work for OOXML files where thumbnailing was enabled, + * which isn't on by default on Windows, but is more common on Mac. + * + * @author Nick Burch + * @since 4.0.1 + */ +public class OOXMLThumbnailContentTransformer extends AbstractContentTransformer2 +{ + private static final Log log = LogFactory.getLog(OOXMLThumbnailContentTransformer.class); + + private static final List OOXML_MIMETYPES = Arrays.asList(new String[]{MimetypeMap.MIMETYPE_OPENXML_SPREADSHEET, + MimetypeMap.MIMETYPE_OPENXML_PRESENTATION, + MimetypeMap.MIMETYPE_OPENXML_WORDPROCESSING}); + private static final List TARGET_MIMETYPES = Arrays.asList(new String[]{MimetypeMap.MIMETYPE_IMAGE_JPEG}); + + @Override + public boolean isTransformable(String sourceMimetype, String targetMimetype, TransformationOptions options) + { + // only support [OOXML] -> JPEG + return TARGET_MIMETYPES.contains(targetMimetype) && OOXML_MIMETYPES.contains(sourceMimetype); + } + + @Override + protected void transformInternal(ContentReader reader, + ContentWriter writer, + TransformationOptions options) throws Exception + { + final String sourceMimetype = reader.getMimetype(); + final String sourceExtension = getMimetypeService().getExtension(sourceMimetype); + final String targetMimetype = writer.getMimetype(); + + + if (log.isDebugEnabled()) + { + StringBuilder msg = new StringBuilder(); + msg.append("Transforming from ").append(sourceMimetype) + .append(" to ").append(targetMimetype); + log.debug(msg.toString()); + } + + + OPCPackage pkg = null; + try + { + File ooxmlTempFile = TempFileProvider.createTempFile(this.getClass().getSimpleName() + "_ooxml", sourceExtension); + reader.getContent(ooxmlTempFile); + + // Load the file + pkg = OPCPackage.open(ooxmlTempFile.getPath()); + + // Does it have a thumbnail? + PackageRelationshipCollection rels = + pkg.getRelationshipsByType(PackageRelationshipTypes.THUMBNAIL); + if (rels.size() > 0) + { + // Get the thumbnail part + PackageRelationship tRel = rels.getRelationship(0); + PackagePart tPart = pkg.getPart(tRel); + + // Write it to the target + InputStream tStream = tPart.getInputStream(); + writer.putContent( tStream ); + tStream.close(); + } + else + { + log.debug("No thumbnail present in " + reader.toString()); + throw new AlfrescoRuntimeException("No thumbnail present in file, unable to generate " + targetMimetype); + } + } + catch (IOException e) + { + throw new AlfrescoRuntimeException("Unable to transform " + sourceExtension + " file.", e); + } + finally + { + if (pkg != null) + { + pkg.close(); + } + } + } +} diff --git a/source/java/org/alfresco/repo/content/transform/OOXMLThumbnailContentTransformerTest.java b/source/java/org/alfresco/repo/content/transform/OOXMLThumbnailContentTransformerTest.java new file mode 100644 index 0000000000..2b44d83a70 --- /dev/null +++ b/source/java/org/alfresco/repo/content/transform/OOXMLThumbnailContentTransformerTest.java @@ -0,0 +1,63 @@ +/* + * 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.content.transform; + +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.service.cmr.repository.TransformationOptions; + +/** + * Test case for {@link OOXMLThumbnailContentTransformer} content transformer. + * + * @author Nick Burch + * @since 4.0.1 + */ +public class OOXMLThumbnailContentTransformerTest extends AbstractContentTransformerTest +{ + private ContentTransformer transformer; + + @Override + public void setUp() throws Exception + { + super.setUp(); + + transformer = new OOXMLThumbnailContentTransformer(); + + // Ugly cast just to set the MimetypeService + ((ContentTransformerHelper)transformer).setMimetypeService(mimetypeService); + } + + @Override + protected ContentTransformer getTransformer(String sourceMimetype, String targetMimetype) + { + return transformer; + } + + public void testIsTransformable() throws Exception + { + // Does support Thumbnails + assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_OPENXML_SPREADSHEET, MimetypeMap.MIMETYPE_IMAGE_JPEG, new TransformationOptions())); + assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_OPENXML_PRESENTATION, MimetypeMap.MIMETYPE_IMAGE_JPEG, new TransformationOptions())); + assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_OPENXML_WORDPROCESSING, MimetypeMap.MIMETYPE_IMAGE_JPEG, new TransformationOptions())); + + // Unlike iWorks, it doesn't handle PDF previews + assertFalse(transformer.isTransformable(MimetypeMap.MIMETYPE_OPENXML_SPREADSHEET, MimetypeMap.MIMETYPE_PDF, new TransformationOptions())); + assertFalse(transformer.isTransformable(MimetypeMap.MIMETYPE_OPENXML_PRESENTATION, MimetypeMap.MIMETYPE_PDF, new TransformationOptions())); + assertFalse(transformer.isTransformable(MimetypeMap.MIMETYPE_OPENXML_WORDPROCESSING, MimetypeMap.MIMETYPE_PDF, new TransformationOptions())); + } +} diff --git a/source/java/org/alfresco/repo/dictionary/DictionaryModelType.java b/source/java/org/alfresco/repo/dictionary/DictionaryModelType.java index 83576d1346..7b6700cb81 100644 --- a/source/java/org/alfresco/repo/dictionary/DictionaryModelType.java +++ b/source/java/org/alfresco/repo/dictionary/DictionaryModelType.java @@ -887,7 +887,7 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda for (WorkflowTaskDefinition workflowTaskDef : workflowService.getTaskDefinitions(workflowDef.getId())) { TypeDefinition workflowTypeDef = workflowTaskDef.metadata; - if (workflowTypeDef.getName().toString().equals(className)) + if (workflowTypeDef.getName().equals(className)) { throw new AlfrescoRuntimeException("Failed to validate model delete" + tenantDomain + " - found task definition in workflow " + workflowDef.getName() + " with " + classType + " '" + className + "'"); } diff --git a/source/java/org/alfresco/repo/domain/activities/ActivityFeedDAO.java b/source/java/org/alfresco/repo/domain/activities/ActivityFeedDAO.java index 96dc235668..0591779c5f 100644 --- a/source/java/org/alfresco/repo/domain/activities/ActivityFeedDAO.java +++ b/source/java/org/alfresco/repo/domain/activities/ActivityFeedDAO.java @@ -47,8 +47,12 @@ public interface ActivityFeedDAO extends ActivitiesDAO public int deleteSiteFeedEntries(String siteUserId) throws SQLException; public List selectFeedsToClean(int maxFeedSize) throws SQLException; - + + public Long countUserFeedEntries(String feedUserId, String format, String siteId, boolean excludeThisUser, boolean excludeOtherUsers, long minFeedId, int maxFeedItems) throws SQLException; + public List selectUserFeedEntries(String feedUserId, String format, String siteId, boolean excludeThisUser, boolean excludeOtherUsers, long minFeedId, int maxFeedItems) throws SQLException; - + + public Long countSiteFeedEntries(String siteUserId, String format, int maxFeedItems) throws SQLException; + public List selectSiteFeedEntries(String siteUserId, String format, int maxFeedItems) throws SQLException; } diff --git a/source/java/org/alfresco/repo/domain/activities/ActivityFeedQueryEntity.java b/source/java/org/alfresco/repo/domain/activities/ActivityFeedQueryEntity.java index 0544715cc7..141fcc0b01 100644 --- a/source/java/org/alfresco/repo/domain/activities/ActivityFeedQueryEntity.java +++ b/source/java/org/alfresco/repo/domain/activities/ActivityFeedQueryEntity.java @@ -27,6 +27,7 @@ package org.alfresco.repo.domain.activities; public class ActivityFeedQueryEntity { private Long minId; + private Long maxId; private String activitySummaryFormat; private String feedUserId; private String siteNetwork; @@ -40,8 +41,18 @@ public class ActivityFeedQueryEntity { this.minId = minId; } - - public String getActivitySummaryFormat() + + public Long getMaxId() + { + return maxId; + } + + public void setMaxId(Long maxId) + { + this.maxId = maxId; + } + + public String getActivitySummaryFormat() { return activitySummaryFormat; } diff --git a/source/java/org/alfresco/repo/domain/activities/ibatis/ActivityFeedDAOImpl.java b/source/java/org/alfresco/repo/domain/activities/ibatis/ActivityFeedDAOImpl.java index 4228f2f49f..46c34975fd 100644 --- a/source/java/org/alfresco/repo/domain/activities/ibatis/ActivityFeedDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/activities/ibatis/ActivityFeedDAOImpl.java @@ -86,6 +86,70 @@ public class ActivityFeedDAOImpl extends ActivitiesDAOImpl implements ActivityFe return (List)template.selectList("alfresco.activities.select_activity_feed_greater_than_max", maxFeedSize); } + @SuppressWarnings("unchecked") + public Long countUserFeedEntries(String feedUserId, String format, String siteId, boolean excludeThisUser, boolean excludeOtherUsers, long minFeedId, int maxFeedSize) throws SQLException + { + ActivityFeedQueryEntity params = new ActivityFeedQueryEntity(); + params.setFeedUserId(feedUserId); + params.setActivitySummaryFormat(format); + + if (minFeedId > -1) + { + params.setMinId(minFeedId); + } + + if (siteId != null) + { + if (excludeThisUser && excludeOtherUsers) + { + return Long.valueOf(0); + } + if ((!excludeThisUser) && (!excludeOtherUsers)) + { + // no excludes => everyone => where feed user is me + return (Long)template.selectOne("alfresco.activities.count_activity_feed_for_feeduser_and_site", params); + } + else if ((excludeThisUser) && (!excludeOtherUsers)) + { + // exclude feed user => others => where feed user is me and post user is not me + return (Long)template.selectOne("alfresco.activities.count_activity_feed_for_feeduser_others_and_site", params); + } + else if ((excludeOtherUsers) && (!excludeThisUser)) + { + // exclude others => me => where feed user is me and post user is me + return (Long)template.selectOne("alfresco.activities.count_activity_feed_for_feeduser_me_and_site", params); + } + } + else + { + // all sites + + if (excludeThisUser && excludeOtherUsers) + { + // effectively NOOP - return empty feed + return Long.valueOf(0); + } + if (!excludeThisUser && !excludeOtherUsers) + { + // no excludes => everyone => where feed user is me + return (Long)template.selectOne("alfresco.activities.count_activity_feed_for_feeduser", params); + } + else if (excludeThisUser) + { + // exclude feed user => others => where feed user is me and post user is not me + return (Long)template.selectOne("alfresco.activities.count_activity_feed_for_feeduser_others", params); + } + else if (excludeOtherUsers) + { + // exclude others => me => where feed user is me and post user is me + return (Long)template.selectOne("alfresco.activities.count_activity_feed_for_feeduser_me", params); + } + } + + // belts-and-braces + throw new AlfrescoRuntimeException("Unexpected: invalid arguments"); + } + @SuppressWarnings("unchecked") public List selectUserFeedEntries(String feedUserId, String format, String siteId, boolean excludeThisUser, boolean excludeOtherUsers, long minFeedId, int maxFeedSize) throws SQLException { @@ -156,7 +220,18 @@ public class ActivityFeedDAOImpl extends ActivitiesDAOImpl implements ActivityFe // belts-and-braces throw new AlfrescoRuntimeException("Unexpected: invalid arguments"); } - + + @SuppressWarnings("unchecked") + public Long countSiteFeedEntries(String siteId, String format, int maxFeedSize) throws SQLException + { + ActivityFeedQueryEntity params = new ActivityFeedQueryEntity(); + params.setSiteNetwork(siteId); + params.setActivitySummaryFormat(format); + + // for given site + return (Long)template.selectOne("alfresco.activities.count_activity_feed_for_site", params); + } + @SuppressWarnings("unchecked") public List selectSiteFeedEntries(String siteId, String format, int maxFeedSize) throws SQLException { diff --git a/source/java/org/alfresco/repo/domain/hibernate/dialect/AlfrescoSQLServerDialect.java b/source/java/org/alfresco/repo/domain/hibernate/dialect/AlfrescoSQLServerDialect.java index 42c772a52d..db55d2b396 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/dialect/AlfrescoSQLServerDialect.java +++ b/source/java/org/alfresco/repo/domain/hibernate/dialect/AlfrescoSQLServerDialect.java @@ -29,6 +29,7 @@ public class AlfrescoSQLServerDialect extends SQLServerDialect { super(); registerColumnType( Types.VARCHAR, "nvarchar($l)" ); + registerColumnType(Types.CLOB, "nvarchar(max)"); } } diff --git a/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java b/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java index 14d7364a5d..dce9a1588e 100644 --- a/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java @@ -57,9 +57,9 @@ import org.alfresco.repo.domain.usage.UsageDAO; import org.alfresco.repo.policy.BehaviourFilter; import org.alfresco.repo.security.permissions.AccessControlListProperties; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; -import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; import org.alfresco.repo.transaction.TransactionAwareSingleton; import org.alfresco.repo.transaction.TransactionListenerAdapter; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.dictionary.InvalidTypeException; @@ -73,20 +73,20 @@ import org.alfresco.service.cmr.repository.DuplicateChildNodeNameException; import org.alfresco.service.cmr.repository.InvalidNodeRefException; import org.alfresco.service.cmr.repository.InvalidStoreRefException; import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeRef.Status; import org.alfresco.service.cmr.repository.Path; import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.repository.NodeRef.Status; import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; import org.alfresco.service.namespace.QName; import org.alfresco.service.transaction.ReadOnlyServerException; import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.EqualsHelper; -import org.alfresco.util.EqualsHelper.MapValueComparison; import org.alfresco.util.GUID; import org.alfresco.util.Pair; import org.alfresco.util.PropertyCheck; import org.alfresco.util.ReadWriteLockExecuter; import org.alfresco.util.SerializationUtils; +import org.alfresco.util.EqualsHelper.MapValueComparison; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.dao.ConcurrencyFailureException; @@ -944,7 +944,38 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO { NodeEntity node = new NodeEntity(nodeRef); Pair pair = nodesCache.getByValue(node); - return (pair == null || pair.getSecond().getDeleted()) ? null : pair.getSecond().getNodePair(); + // The noderef is currently invalid WRT to the cache. Let's just check the database + if (pair == null || pair.getSecond().getDeleted()) + { + Node dbNode = selectNodeByNodeRef(nodeRef, null); + if (dbNode == null) + { + // The DB agrees. This is an invalid noderef. Why are you trying to use it? + return null; + } + Long nodeId = dbNode.getId(); + if (dbNode.getDeleted()) + { + // The node is actually deleted as the cache said. Could still be a race condition, so let's allow the + // transaction to be retried by attaching a cause to our InvalidNodeRefException + InvalidNodeRefException e = new InvalidNodeRefException(nodeRef); + e.initCause(new ConcurrencyFailureException("Attempt to follow reference " + nodeRef + + " to deleted node " + nodeId)); + throw e; + } + else + { + // The cache was wrong, possibly due to it caching negative results earlier. Let's repair it and carry on! + if (logger.isDebugEnabled()) + { + logger.debug("Stale cache detected for Node " + nodeRef + ": previously though to be deleted. Repairing cache."); + } + invalidateNodeCaches(nodeId); + nodesCache.setValue(dbNode.getId(), dbNode); + return dbNode.getNodePair(); + } + } + return pair.getSecond().getNodePair(); } public Pair getNodePair(Long nodeId) @@ -2793,16 +2824,21 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO catch (Throwable e) { controlDAO.rollbackToSavepoint(savepoint); - // SQL Server retry - if(e.getMessage().contains("Snapshot isolation transaction aborted")) + // SQL Server retry + if (e.getMessage().contains("Snapshot isolation transaction aborted")) { + logger.warn("insertChildAssoc: SQL Server snapshot isolation retry: "+assoc); throw new ConcurrencyFailureException("SQL Server snapshot isolation retry...", e); } - if(e.getMessage().contains("The INSERT statement conflicted with the FOREIGN KEY constraint")) + // FK conflict retry, eg. + // SQL Server - The INSERT statement conflicted with the FOREIGN KEY constraint + // MySQL - Cannot add or update a child row: a foreign key constraint fails + if (e.getMessage().toUpperCase().contains("FOREIGN KEY")) { - throw new ConcurrencyFailureException("SQL Server FK conflict retry...", e); + logger.warn("insertChildAssoc: FK conflict retry: "+assoc); + throw new ConcurrencyFailureException("FK conflict retry...", e); } // We assume that this is from the child cm:name constraint violation @@ -3643,7 +3679,18 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO // Now check if we are seeing the correct version of the node if (assocs.isEmpty()) { - // No results. We can draw no conclusions. + // No results. Currently Alfresco has very few parentless nodes (root nodes) + // and the lack of parent associations will be cached, anyway. + // But to match earlier fixes of ALF-12393, we do a double-check of the node's details + NodeEntity nodeCheckFromDb = selectNodeById(nodeId, null); + if (nodeCheckFromDb == null || !nodeCheckFromDb.getNodeVersionKey().equals(nodeVersionKey)) + { + // The node is gone or has moved on in version + invalidateNodeCaches(nodeId); + throw new DataIntegrityViolationException( + "Detected stale node entry: " + nodeVersionKey + + " (now " + nodeCheckFromDb + ")"); + } } else { diff --git a/source/java/org/alfresco/repo/domain/node/NodeDAO.java b/source/java/org/alfresco/repo/domain/node/NodeDAO.java index b7087b39b9..d7f1003580 100644 --- a/source/java/org/alfresco/repo/domain/node/NodeDAO.java +++ b/source/java/org/alfresco/repo/domain/node/NodeDAO.java @@ -727,4 +727,12 @@ public interface NodeDAO extends NodeBulkLoader * Used by the re-encryptor to re-encrypt encryptable properties with a new encryption key. */ public List selectProperties(Collection propertyDefs); + + /** + * Counts the number of child associations directly under parentNodeId. + * + * @param parentNodeId the parent node id + * @param isPrimary count just primary associations? + */ + public int countChildAssocsByParent(Long parentNodeId, boolean isPrimary); } diff --git a/source/java/org/alfresco/repo/domain/node/ibatis/NodeDAOImpl.java b/source/java/org/alfresco/repo/domain/node/ibatis/NodeDAOImpl.java index 963dd75631..b92e3223e8 100644 --- a/source/java/org/alfresco/repo/domain/node/ibatis/NodeDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/node/ibatis/NodeDAOImpl.java @@ -118,6 +118,7 @@ public class NodeDAOImpl extends AbstractNodeDAOImpl private static final String UPDATE_CHILD_ASSOCS_UNIQUE_NAME = "alfresco.node.update_ChildAssocsUniqueName"; private static final String DELETE_CHILD_ASSOCS_TO_AND_FROM = "alfresco.node.delete_ChildAssocsToAndFrom"; private static final String SELECT_CHILD_ASSOC_BY_ID = "alfresco.node.select_ChildAssocById"; + private static final String COUNT_CHILD_ASSOC_BY_PARENT_ID = "alfresco.node.count_ChildAssocByParentId"; private static final String SELECT_CHILD_ASSOCS_BY_PROPERTY_VALUE = "alfresco.node.select_ChildAssocsByPropertyValue"; private static final String SELECT_CHILD_ASSOCS_OF_PARENT = "alfresco.node.select_ChildAssocsOfParent"; private static final String SELECT_CHILD_ASSOCS_OF_PARENT_LIMITED = "alfresco.node.select.children.select_ChildAssocsOfParent_Limited"; @@ -1034,7 +1035,7 @@ public class NodeDAOImpl extends AbstractNodeDAOImpl } } } - + @Override protected void selectChildAssocs( Long parentNodeId, @@ -1647,6 +1648,16 @@ public class NodeDAOImpl extends AbstractNodeDAOImpl return properties; } + public int countChildAssocsByParent(Long parentNodeId, boolean isPrimary) + { + NodeEntity parentNode = new NodeEntity(); + parentNode.setId(parentNodeId); + ChildAssocEntity childAssoc = new ChildAssocEntity(); + childAssoc.setParentNode(parentNode); + childAssoc.setPrimary(Boolean.valueOf(isPrimary)); + return (Integer)template.selectOne(COUNT_CHILD_ASSOC_BY_PARENT_ID, childAssoc); + } + /* * DAO OVERRIDES */ diff --git a/source/java/org/alfresco/repo/domain/permissions/ADMAccessControlListDAO.java b/source/java/org/alfresco/repo/domain/permissions/ADMAccessControlListDAO.java index c4d505ba48..fc776385e7 100644 --- a/source/java/org/alfresco/repo/domain/permissions/ADMAccessControlListDAO.java +++ b/source/java/org/alfresco/repo/domain/permissions/ADMAccessControlListDAO.java @@ -152,7 +152,7 @@ public class ADMAccessControlListDAO implements AccessControlListDAO Long newDefiningAcl = null; - if((existingNodeAclId != null) && (existingNodeAclId == inheritedAclId)) + if((existingNodeAclId != null) && (existingNodeAclId.equals(inheritedAclId))) { // nothing to do except move into the children } diff --git a/source/java/org/alfresco/repo/domain/permissions/AVMAccessControlListDAO.java b/source/java/org/alfresco/repo/domain/permissions/AVMAccessControlListDAO.java index 230cf0b3f3..90eed13a5d 100644 --- a/source/java/org/alfresco/repo/domain/permissions/AVMAccessControlListDAO.java +++ b/source/java/org/alfresco/repo/domain/permissions/AVMAccessControlListDAO.java @@ -646,7 +646,7 @@ public class AVMAccessControlListDAO implements AccessControlListDAO else if (changes.containsKey(id)) { Long updateId = changes.get(id); - if (updateId != id) + if (!id.equals(updateId)) { Acl newAcl = aclDaoComponent.getAcl(updateId); // No need to force COW - ACL should have COWed if required diff --git a/source/java/org/alfresco/repo/domain/permissions/AclDAOImpl.java b/source/java/org/alfresco/repo/domain/permissions/AclDAOImpl.java index 46b753b2cf..4d7d9553e3 100644 --- a/source/java/org/alfresco/repo/domain/permissions/AclDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/permissions/AclDAOImpl.java @@ -780,6 +780,7 @@ public class AclDAOImpl implements AclDAO if (nodePair == null) { logger.warn("Node does not exist: " + nodeId); + continue; } NodeRef nodeRef = nodePair.getSecond(); @@ -928,7 +929,7 @@ public class AclDAOImpl implements AclDAO Acl unusedInherited = null; for (AclChange change : acls) { - if (change.getBefore() == inherited.getId()) + if (change.getBefore()!= null && change.getBefore().equals(inherited.getId())) { unusedInherited = aclCrudDAO.getAcl(change.getAfter()); } @@ -1229,7 +1230,7 @@ public class AclDAOImpl implements AclDAO Acl test = inheritedAcl; while (test != null) { - if (test.getId() == target) + if (test.getId()!= null && test.getId().equals(target)) { throw new IllegalStateException("Cyclical ACL detected"); } @@ -1433,7 +1434,7 @@ public class AclDAOImpl implements AclDAO } return toCopy; case REDIRECT: - if ((toInheritFrom != null) && (toInheritFrom == toCopy)) + if ((toInheritFrom != null) && (toInheritFrom.equals(toCopy))) { return getInheritedAccessControlList(toInheritFrom); } diff --git a/source/java/org/alfresco/repo/domain/propval/AbstractPropertyValueDAOImpl.java b/source/java/org/alfresco/repo/domain/propval/AbstractPropertyValueDAOImpl.java index 784283cbe0..42156e1bc9 100644 --- a/source/java/org/alfresco/repo/domain/propval/AbstractPropertyValueDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/propval/AbstractPropertyValueDAOImpl.java @@ -21,6 +21,7 @@ package org.alfresco.repo.domain.propval; import java.io.Serializable; import java.sql.Savepoint; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.HashMap; @@ -1173,7 +1174,7 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO { logger.debug( "Searched for unique property context: \n" + - " Values: " + values); + " Values: " + Arrays.toString(values)); } } @@ -1276,8 +1277,8 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO { logger.debug( "Deleted " + deleted + " unique property contexts: \n" + - " Values: " + values + "\n" + - " IDs: " + valueIds); + " Values: " + Arrays.toString(values) + "\n" + + " IDs: " + Arrays.toString(valueIds)); } return deleted; } diff --git a/source/java/org/alfresco/repo/domain/schema/SchemaBootstrap.java b/source/java/org/alfresco/repo/domain/schema/SchemaBootstrap.java index 58aeb73ce6..b6d9267c3c 100644 --- a/source/java/org/alfresco/repo/domain/schema/SchemaBootstrap.java +++ b/source/java/org/alfresco/repo/domain/schema/SchemaBootstrap.java @@ -71,9 +71,12 @@ import org.alfresco.repo.domain.hibernate.dialect.AlfrescoOracle9Dialect; import org.alfresco.repo.domain.hibernate.dialect.AlfrescoSQLServerDialect; import org.alfresco.repo.domain.hibernate.dialect.AlfrescoSybaseAnywhereDialect; import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.descriptor.DescriptorService; import org.alfresco.util.LogUtil; import org.alfresco.util.TempFileProvider; import org.alfresco.util.schemacomp.ExportDb; +import org.alfresco.util.schemacomp.MultiFileDumper; +import org.alfresco.util.schemacomp.MultiFileDumper.DbToXMLFactory; import org.alfresco.util.schemacomp.Result; import org.alfresco.util.schemacomp.Results; import org.alfresco.util.schemacomp.SchemaComparator; @@ -214,6 +217,18 @@ public class SchemaBootstrap extends AbstractLifecycleBean } } + + /** + * Provide a reference to the DescriptorService, used to provide information + * about the repository such as the database schema version number. + * + * @param descriptorService the descriptorService to set + */ + public void setDescriptorService(DescriptorService descriptorService) + { + this.descriptorService = descriptorService; + } + /** * Sets the previously auto-detected Hibernate dialect. * @@ -227,6 +242,7 @@ public class SchemaBootstrap extends AbstractLifecycleBean private static Log logger = LogFactory.getLog(SchemaBootstrap.class); + private DescriptorService descriptorService; private DataSource dataSource; private LocalSessionFactoryBean localSessionFactory; private String schemaOuputFilename; @@ -234,7 +250,7 @@ public class SchemaBootstrap extends AbstractLifecycleBean private boolean stopAfterSchemaBootstrap; private List preCreateScriptUrls; private List postCreateScriptUrls; - private String schemaReferenceUrl; + private List schemaReferenceUrls; private List validateUpdateScriptPatches; private List preUpdateScriptPatches; private List postUpdateScriptPatches; @@ -244,7 +260,7 @@ public class SchemaBootstrap extends AbstractLifecycleBean private Properties globalProperties; private ThreadLocal executedStatementsThreadLocal = new ThreadLocal(); - private File xmlPreSchemaOutputFile; // This must be set if there are any executed statements + private File xmlPreSchemaOutputFile34; // This must be set if there are any executed statements public SchemaBootstrap() { @@ -334,17 +350,17 @@ public class SchemaBootstrap extends AbstractLifecycleBean /** - * Specifies the schema reference file that will be used to validate the repository + * Specifies the schema reference files that will be used to validate the repository * schema whenever changes have been made. The database dialect placeholder will be - * resolved so that the correct reference file is loaded for the current database + * resolved so that the correct reference files are loaded for the current database * type (e.g. PostgreSQL) * - * @param schemaReferenceUrl the schemaReferenceUrl to set + * @param schemaReferenceUrls the schemaReferenceUrls to set * @see #PLACEHOLDER_DIALECT */ - public void setSchemaReferenceUrl(String schemaReferenceUrl) + public void setSchemaReferenceUrls(List schemaReferenceUrls) { - this.schemaReferenceUrl = schemaReferenceUrl; + this.schemaReferenceUrls = schemaReferenceUrls; } /** @@ -629,7 +645,7 @@ public class SchemaBootstrap extends AbstractLifecycleBean /** * @return Returns the number of applied patches */ - private boolean didPatchSucceed(Connection connection, String patchId) throws Exception + private boolean didPatchSucceed(Connection connection, String patchId, boolean alternative) throws Exception { String patchTableName = getAppliedPatchTableName(connection); if (patchTableName == null) @@ -640,13 +656,22 @@ public class SchemaBootstrap extends AbstractLifecycleBean Statement stmt = connection.createStatement(); try { - ResultSet rs = stmt.executeQuery("select succeeded from " + patchTableName + " where id = '" + patchId + "'"); + ResultSet rs = stmt.executeQuery("select succeeded, was_executed from " + patchTableName + " where id = '" + patchId + "'"); if (!rs.next()) { return false; } boolean succeeded = rs.getBoolean(1); - return succeeded; + boolean wasExecuted = rs.getBoolean(2); + + if (alternative) + { + return succeeded && wasExecuted; + } + else + { + return succeeded; + } } finally { @@ -911,7 +936,7 @@ public class SchemaBootstrap extends AbstractLifecycleBean for (Patch alternativePatch : alternatives) { String alternativePatchId = alternativePatch.getId(); - boolean alternativeSucceeded = didPatchSucceed(connection, alternativePatchId); + boolean alternativeSucceeded = didPatchSucceed(connection, alternativePatchId, true); if (alternativeSucceeded) { continue nextPatch; @@ -919,7 +944,7 @@ public class SchemaBootstrap extends AbstractLifecycleBean } // check if the script was successfully executed - boolean wasSuccessfullyApplied = didPatchSucceed(connection, patchId); + boolean wasSuccessfullyApplied = didPatchSucceed(connection, patchId, false); if (wasSuccessfullyApplied) { // Either the patch was executed before or the system was bootstrapped @@ -979,7 +1004,7 @@ public class SchemaBootstrap extends AbstractLifecycleBean private Resource getDialectResource(Class dialectClass, String resourceUrl) { // replace the dialect placeholder - String dialectResourceUrl = resourceUrl.replaceAll(PLACEHOLDER_DIALECT, dialectClass.getName()); + String dialectResourceUrl = resolveDialectUrl(dialectClass, resourceUrl); // get a handle on the resource Resource resource = rpr.getResource(dialectResourceUrl); if (!resource.exists()) @@ -1003,6 +1028,28 @@ public class SchemaBootstrap extends AbstractLifecycleBean return resource; } } + + /** + * Takes resource URL containing the {@link SchemaBootstrap#PLACEHOLDER_DIALECT dialect placeholder text} + * and substitutes the placeholder with the name of the given dialect's class. + *

+ * For example: + *

+     *   resolveDialectUrl(MySQLInnoDBDialect.class, "classpath:alfresco/db/${db.script.dialect}/myfile.xml")
+     * 
+ * would give the following String: + *
+     *   classpath:alfresco/db/org.hibernate.dialect.MySQLInnoDBDialect/myfile.xml
+     * 
+ * + * @param dialectClass + * @param resourceUrl + * @return + */ + private String resolveDialectUrl(Class dialectClass, String resourceUrl) + { + return resourceUrl.replaceAll(PLACEHOLDER_DIALECT, dialectClass.getName()); + } /** * Replaces the dialect placeholder in the script URL and attempts to find a file for @@ -1035,15 +1082,17 @@ public class SchemaBootstrap extends AbstractLifecycleBean if (executedStatements == null) { // Validate the schema, pre-upgrade - validateSchema("Alfresco-{0}-Validation-Pre-Upgrade-"); + validateSchema("Alfresco-{0}-Validation-Pre-Upgrade-{1}-"); // Dump the normalized, pre-upgrade Alfresco schema. We keep the file for later reporting. - xmlPreSchemaOutputFile = dumpSchema( + xmlPreSchemaOutputFile34 = dumpSchema34( this.dialect, TempFileProvider.createTempFile( "AlfrescoSchema-" + this.dialect.getClass().getSimpleName() + "-", "-Startup.xml").getPath(), "Failed to dump normalized, pre-upgrade schema to file."); + + dumpSchema("pre-upgrade"); // There is no lock at this stage. This process can fall out if the lock can't be applied. setBootstrapStarted(connection); @@ -1538,34 +1587,37 @@ public class SchemaBootstrap extends AbstractLifecycleBean if (executedStatements != null) { // Validate the schema, post-upgrade - validateSchema("Alfresco-{0}-Validation-Post-Upgrade-"); + validateSchema("Alfresco-{0}-Validation-Post-Upgrade-{1}-"); // Dump the normalized, post-upgrade Alfresco schema. - File xmlPostSchemaOutputFile = dumpSchema( + File xmlPostSchemaOutputFile34 = dumpSchema34( this.dialect, TempFileProvider.createTempFile( "AlfrescoSchema-" + this.dialect.getClass().getSimpleName() + "-", ".xml").getPath(), "Failed to dump normalized, post-upgrade schema to file."); + // 4.0+ schema dump + dumpSchema("post-upgrade"); + if (createdSchema) { // This is a new schema - if (xmlPostSchemaOutputFile != null) + if (xmlPostSchemaOutputFile34 != null) { - LogUtil.info(logger, MSG_NORMALIZED_SCHEMA, xmlPostSchemaOutputFile.getPath()); + LogUtil.info(logger, MSG_NORMALIZED_SCHEMA, xmlPostSchemaOutputFile34.getPath()); } } else { // We upgraded, so have to report pre- and post- schema dumps - if (xmlPreSchemaOutputFile != null) + if (xmlPreSchemaOutputFile34 != null) { - LogUtil.info(logger, MSG_NORMALIZED_SCHEMA_PRE, xmlPreSchemaOutputFile.getPath()); + LogUtil.info(logger, MSG_NORMALIZED_SCHEMA_PRE, xmlPreSchemaOutputFile34.getPath()); } - if (xmlPostSchemaOutputFile != null) + if (xmlPostSchemaOutputFile34 != null) { - LogUtil.info(logger, MSG_NORMALIZED_SCHEMA_POST, xmlPostSchemaOutputFile.getPath()); + LogUtil.info(logger, MSG_NORMALIZED_SCHEMA_POST, xmlPostSchemaOutputFile34.getPath()); } } } @@ -1579,12 +1631,16 @@ public class SchemaBootstrap extends AbstractLifecycleBean { // We have been forced to stop, so we do one last dump of the schema and throw an exception to // escape further startup procedures - File xmlStopSchemaOutputFile = dumpSchema( + File xmlStopSchemaOutputFile = dumpSchema34( this.dialect, TempFileProvider.createTempFile( "AlfrescoSchema-" + this.dialect.getClass().getSimpleName() + "-", "-ForcedExit.xml").getPath(), "Failed to dump normalized, post-upgrade, forced-exit schema to file."); + + // 4.0+ schema dump + dumpSchema("forced-exit"); + if (xmlStopSchemaOutputFile != null) { LogUtil.info(logger, MSG_NORMALIZED_SCHEMA, xmlStopSchemaOutputFile); @@ -1639,19 +1695,55 @@ public class SchemaBootstrap extends AbstractLifecycleBean * Collate differences and validation problems with the schema with respect to an appropriate * reference schema. * + * @param outputFileNameTemplate * @return the number of potential problems found. */ - public int validateSchema(String outputFileNameTemplate) + public synchronized int validateSchema(String outputFileNameTemplate) { - Date startTime = new Date(); + int totalProblems = 0; - Resource referenceResource = getDialectResource(dialect.getClass(), schemaReferenceUrl); - if (referenceResource == null || !referenceResource.exists()) + // Discover available reference files (e.g. for prefixes alf_, avm_ etc.) + // and process each in turn. + for (String schemaReferenceUrl : schemaReferenceUrls) { - String resourceUrl = schemaReferenceUrl.replaceAll(PLACEHOLDER_DIALECT, dialect.getClass().getName()); - LogUtil.debug(logger, DEBUG_SCHEMA_COMP_NO_REF_FILE, resourceUrl); + Resource referenceResource = getDialectResource(dialect.getClass(), schemaReferenceUrl); + + if (referenceResource == null || !referenceResource.exists()) + { + String resourceUrl = resolveDialectUrl(dialect.getClass(), schemaReferenceUrl); + LogUtil.debug(logger, DEBUG_SCHEMA_COMP_NO_REF_FILE, resourceUrl); + } + else + { + // Validate schema against each reference file + int problems = validateSchema(referenceResource, outputFileNameTemplate); + totalProblems += problems; + } + } + + // Return number of problems found across all reference files. + return totalProblems; + } + + private int validateSchema(Resource referenceResource, String outputFileNameTemplate) + { + try + { + return attemptValidateSchema(referenceResource, outputFileNameTemplate); + } + catch (Throwable e) + { + if (logger.isErrorEnabled()) + { + logger.error("Unable to validate database schema.", e); + } return 0; } + } + + private int attemptValidateSchema(Resource referenceResource, String outputFileNameTemplate) + { + Date startTime = new Date(); InputStream is = null; try @@ -1667,7 +1759,10 @@ public class SchemaBootstrap extends AbstractLifecycleBean xmlToSchema.parse(); Schema reference = xmlToSchema.getSchema(); - ExportDb exporter = new ExportDb(dataSource, dialect); + ExportDb exporter = new ExportDb(dataSource, dialect, descriptorService); + // Ensure that the database objects we're validating are filtered + // by the same prefix as the reference file. + exporter.setNamePrefix(reference.getDbPrefix()); exporter.execute(); Schema target = exporter.getSchema(); @@ -1677,9 +1772,12 @@ public class SchemaBootstrap extends AbstractLifecycleBean Results results = schemaComparator.getComparisonResults(); - String outputFileName = MessageFormat.format( - outputFileNameTemplate, - new Object[] { dialect.getClass().getSimpleName() }); + Object[] outputFileNameParams = new Object[] + { + dialect.getClass().getSimpleName(), + reference.getDbPrefix() + }; + String outputFileName = MessageFormat.format(outputFileNameTemplate, outputFileNameParams); File outputFile = TempFileProvider.createTempFile(outputFileName, ".txt"); @@ -1723,9 +1821,13 @@ public class SchemaBootstrap extends AbstractLifecycleBean } /** + * Older schema dump tool. Left here for the moment but is essentially duplicated + * by the newer XML dumper which is used in automatic diff/validation of the schema + * against a known good reference. + * * @return Returns the file that was written to or null if it failed */ - private File dumpSchema(Dialect dialect, String fileName, String err) + private File dumpSchema34(Dialect dialect, String fileName, String err) { File xmlSchemaOutputFile = new File(fileName); try @@ -1749,6 +1851,121 @@ public class SchemaBootstrap extends AbstractLifecycleBean return xmlSchemaOutputFile; } + /** + * Produces schema dump in XML format: this is performed pre- and post-upgrade (i.e. if + * changes are made to the schema) and can made upon demand via JMX. + * + * @return List of output files. + */ + public List dumpSchema() + { + return dumpSchema("", null); + } + + /** + * Produces schema dump in XML format: this is performed pre- and post-upgrade (i.e. if + * changes are made to the schema) and can made upon demand via JMX. + * + * @param dbPrefixes Array of database object prefixes to produce the dump for, e.g. "alf_". + * @return List of output files. + */ + public List dumpSchema(String[] dbPrefixes) + { + return dumpSchema("", dbPrefixes); + } + + /** + * Dumps the DB schema to temporary file(s), named similarly to: + *
+     *   Alfresco-schema-DialectName-whenDumped-dbPrefix-23498732.xml
+     * 
+ * Where the digits serve to create a unique temp file name. If whenDumped is empty or null, + * then the output is similar to: + *
+     *   Alfresco-schema-DialectName-dbPrefix-23498732.xml
+     * 
+ * If dbPrefixes is null, then the default list is used (see {@link MultiFileDumper#DEFAULT_PREFIXES}) + * The dump files' paths are logged at info level. + * + * @param whenDumped + * @param dbPrefixes Array of database object prefixes to filter by, e.g. "alf_" + * @return List of output files. + */ + private List dumpSchema(String whenDumped, String[] dbPrefixes) + { + // Build a string to use as the file name template, + // e.g. "Alfresco-schema-MySQLDialect-pre-upgrade-{0}-" + StringBuilder sb = new StringBuilder(64); + sb.append("Alfresco-schema-"). + append(dialect.getClass().getSimpleName()); + if (whenDumped != null && whenDumped.length() > 0) + { + sb.append("-"); + sb.append(whenDumped); + } + sb.append("-{0}-"); + + File outputDir = TempFileProvider.getTempDir(); + String fileNameTemplate = sb.toString(); + return dumpSchema(outputDir, fileNameTemplate, dbPrefixes); + } + + /** + * Same as for {@link #dumpSchema(String, String[])} - except that the default list + * of database object prefixes is used for filtering. + * + * @see #dumpSchema(String, String[]) + * @param whenDumped + * @return + */ + private List dumpSchema(String whenDumped) + { + return dumpSchema(whenDumped, null); + } + + private List dumpSchema(File outputDir, String fileNameTemplate, String[] dbPrefixes) + { + try + { + return attemptDumpSchema(outputDir, fileNameTemplate, dbPrefixes); + } + catch (Throwable e) + { + if (logger.isErrorEnabled()) + { + logger.error("Unable to dump schema to directory " + outputDir, e); + } + return null; + } + } + + private List attemptDumpSchema(File outputDir, String fileNameTemplate, String[] dbPrefixes) + { + DbToXMLFactory dbToXMLFactory = new MultiFileDumper.DbToXMLFactoryImpl(getApplicationContext()); + + MultiFileDumper dumper; + + if (dbPrefixes == null) + { + dumper = new MultiFileDumper(outputDir, fileNameTemplate, dbToXMLFactory); + } + else + { + dumper = new MultiFileDumper(dbPrefixes, outputDir, fileNameTemplate, dbToXMLFactory); + } + List files = dumper.dumpFiles(); + + for (File file : files) + { + if (logger.isInfoEnabled()) + { + LogUtil.info(logger, MSG_NORMALIZED_SCHEMA, file.getAbsolutePath()); + } + } + + return files; + } + @Override protected void onShutdown(ApplicationEvent event) { diff --git a/source/java/org/alfresco/repo/imap/ImapServiceImpl.java b/source/java/org/alfresco/repo/imap/ImapServiceImpl.java index e2631a9330..4066e368c4 100644 --- a/source/java/org/alfresco/repo/imap/ImapServiceImpl.java +++ b/source/java/org/alfresco/repo/imap/ImapServiceImpl.java @@ -84,6 +84,7 @@ import org.alfresco.service.cmr.model.FileInfo; import org.alfresco.service.cmr.model.FileNotFoundException; import org.alfresco.service.cmr.model.SubFolderFilter; import org.alfresco.service.cmr.preference.PreferenceService; +import org.alfresco.service.cmr.repository.AssociationRef; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.ContentWriter; @@ -95,6 +96,7 @@ import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.cmr.security.AccessStatus; import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.cmr.site.SiteInfo; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; @@ -882,7 +884,10 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol logger.debug("Subscribing: " + user + ", " + mailbox); } AlfrescoImapFolder mailFolder = getOrCreateMailbox(user, mailbox, true, false); - nodeService.removeAspect(mailFolder.getFolderInfo().getNodeRef(), ImapModel.ASPECT_IMAP_FOLDER_NONSUBSCRIBED); + PersonService personService = serviceRegistry.getPersonService(); + NodeRef userRef = personService.getPerson(user.getLogin()); + + nodeService.removeAssociation(userRef, mailFolder.getFolderInfo().getNodeRef(), ImapModel.ASSOC_IMAP_UNSUBSCRIBED); } public void unsubscribe(AlfrescoImapUser user, String mailbox) @@ -894,8 +899,9 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol AlfrescoImapFolder mailFolder = getOrCreateMailbox(user, mailbox, true, false); if(mailFolder.getFolderInfo() != null) { - logger.debug("Unsubscribing by ASPECT_IMAP_FOLDER_NONSUBSCRIBED"); - nodeService.addAspect(mailFolder.getFolderInfo().getNodeRef(), ImapModel.ASPECT_IMAP_FOLDER_NONSUBSCRIBED, null); + PersonService personService = serviceRegistry.getPersonService(); + NodeRef userRef = personService.getPerson(user.getLogin()); + nodeService.createAssociation(userRef, mailFolder.getFolderInfo().getNodeRef(), ImapModel.ASSOC_IMAP_UNSUBSCRIBED); } else { @@ -993,6 +999,9 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol String rootPath = index == -1 ? mailboxPattern : mailboxPattern.substring(0, index); boolean found = false; + String userName = user.getLogin(); + Set unsubscribedFodlers = getUnsubscribedFolders(userName); + for (String mountPointName : imapConfigMountPoints.keySet()) { if (mountPointName.matches(rootPath.replaceAll("[%\\*]", ".*"))) @@ -1005,8 +1014,8 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol ImapViewMode viewMode = imapConfigMountPoints.get(mountPointName).getMode(); if (index < 0) { - String userName = user.getLogin(); - if (!listSubscribed || isSubscribed(mountPointFileInfo, userName)) + + if (!listSubscribed || !unsubscribedFodlers.contains(mountPointFileInfo.getNodeRef())) { result.add(new AlfrescoImapFolder(mountPointFileInfo, userName, mountPointName, mountPointName, viewMode, isExtractionEnabled(mountPointFileInfo.getNodeRef()), serviceRegistry, mountPointId)); @@ -1111,6 +1120,9 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol if (index < 0) { + String userName = user.getLogin(); + Set unsubscribedFodlers = getUnsubscribedFolders(userName); + // This is the last level for (FileInfo fileInfo : list) { @@ -1119,8 +1131,7 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol continue; } String folderPath = rootPathPrefix + fileInfo.getName(); - String userName = user.getLogin(); - if (!listSubscribed || isSubscribed(fileInfo, userName)) + if (!listSubscribed || !unsubscribedFodlers.contains(fileInfo.getNodeRef())) { fullList.add(new AlfrescoImapFolder(fileInfo, userName, fileInfo.getName(), folderPath, viewMode, isExtractionEnabled(fileInfo.getNodeRef()), serviceRegistry, mountPointId)); @@ -1233,11 +1244,21 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol return userHome; } - private boolean isSubscribed(FileInfo fileInfo, String userName) + private Set getUnsubscribedFolders(String userName) { - return !nodeService.hasAspect(fileInfo.getNodeRef(), ImapModel.ASPECT_IMAP_FOLDER_NONSUBSCRIBED); + Set result = new HashSet(); + PersonService personService = serviceRegistry.getPersonService(); + NodeRef userRef = personService.getPerson(userName); + List unsubscribedFodlers = nodeService.getTargetAssocs(userRef, ImapModel.ASSOC_IMAP_UNSUBSCRIBED); + for (AssociationRef asocRef : unsubscribedFodlers) + { + result.add(asocRef.getTargetRef()); + } + + return result; } + private String getCurrentUser() { return AuthenticationUtil.getFullyAuthenticatedUser(); diff --git a/source/java/org/alfresco/repo/invitation/AbstractInvitationServiceImplTest.java b/source/java/org/alfresco/repo/invitation/AbstractInvitationServiceImplTest.java index d747c6f0c2..8fefb6a93e 100644 --- a/source/java/org/alfresco/repo/invitation/AbstractInvitationServiceImplTest.java +++ b/source/java/org/alfresco/repo/invitation/AbstractInvitationServiceImplTest.java @@ -1,1077 +1,1076 @@ -/* - * 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.invitation; - -import java.lang.reflect.Field; -import java.util.Calendar; -import java.util.Collection; -import java.util.Date; -import java.util.List; - -import javax.mail.internet.MimeMessage; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.action.executer.MailActionExecuter; -import org.alfresco.repo.management.subsystems.ApplicationContextFactory; -import org.alfresco.repo.security.authentication.AuthenticationComponent; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.site.SiteModel; -import org.alfresco.repo.workflow.WorkflowAdminServiceImpl; -import org.alfresco.service.cmr.invitation.Invitation; -import org.alfresco.service.cmr.invitation.Invitation.ResourceType; -import org.alfresco.service.cmr.invitation.InvitationSearchCriteria; -import org.alfresco.service.cmr.invitation.InvitationService; -import org.alfresco.service.cmr.invitation.ModeratedInvitation; -import org.alfresco.service.cmr.invitation.NominatedInvitation; -import org.alfresco.service.cmr.security.PersonService; -import org.alfresco.service.cmr.site.SiteInfo; -import org.alfresco.service.cmr.site.SiteService; -import org.alfresco.service.cmr.site.SiteVisibility; -import org.alfresco.util.BaseAlfrescoSpringTest; -import org.alfresco.util.PropertyMap; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.util.ReflectionUtils; - -/** - * Unit tests of Invitation Service - */ -public abstract class AbstractInvitationServiceImplTest extends BaseAlfrescoSpringTest -{ - private static final Log logger = LogFactory.getLog(AbstractInvitationServiceImplTest.class); - - private SiteService siteService; - protected AuthenticationComponent authenticationComponent; - private PersonService personService; - protected InvitationService invitationService; - private MailActionExecuter mailService; - private boolean startSendEmails; - protected InvitationServiceImpl invitationServiceImpl; - protected WorkflowAdminServiceImpl workflowAdminService; - - protected final static String SITE_SHORT_NAME_INVITE = "InvitationTest"; - protected final static String SITE_SHORT_NAME_RED = "InvitationTestRed"; - protected final static String SITE_SHORT_NAME_BLUE = "InvitationTestBlue"; - public final static String PERSON_FIRSTNAME = "InvitationFirstName123"; - public final static String PERSON_FIRSTNAME_SPACES = "Invitation First\tName\n1\r2\r\n3"; - public final static String PERSON_LASTNAME = "InvitationLastName123"; - public final static String PERSON_LASTNAME_SPACES = "Invitation Last\tName\n1\r2\r\n3"; - public final static String PERSON_JOBTITLE = "JobTitle123"; - public final static String PERSON_ORG = "Organisation123"; - - public final static String USER_MANAGER = "InvitationServiceManagerOne"; - public final static String USER_ONE = "InvitationServiceAlice"; - public final static String USER_TWO = "InvitationServiceBob"; - public final static String USER_EVE = "InvitationServiceEve"; - public final static String USER_NOEMAIL = "InvitationServiceNoEmail"; - public final static String USER_ONE_FIRSTNAME = "One"; - public final static String USER_ONE_LASTNAME = "Test"; - public final static String USER_ONE_EMAIL = USER_ONE + "@alfrescotesting.com"; - public final static String USER_TWO_EMAIL = USER_TWO + "@alfrescotesting.com"; - - private Collection enabledEngines; - private Collection visibleEngines; - /** - * Called during the transaction setup - */ - @SuppressWarnings("deprecation") - @Override - protected void onSetUpInTransaction() throws Exception - { - super.onSetUpInTransaction(); - this.invitationService = (InvitationService) this.applicationContext.getBean("InvitationService"); - this.siteService = (SiteService) this.applicationContext.getBean("SiteService"); - this.personService = (PersonService) this.applicationContext.getBean("PersonService"); - this.authenticationComponent = (AuthenticationComponent) this.applicationContext - .getBean("authenticationComponent"); - this.invitationServiceImpl = (InvitationServiceImpl) applicationContext.getBean("invitationService"); - this.workflowAdminService = (WorkflowAdminServiceImpl)applicationContext.getBean(WorkflowAdminServiceImpl.NAME); - - this.startSendEmails = invitationServiceImpl.isSendEmails(); - - this.enabledEngines = workflowAdminService.getEnabledEngines(); - this.visibleEngines = workflowAdminService.getVisibleEngines(); - - invitationServiceImpl.setSendEmails(true); - - // TODO MER 20/11/2009 Bodge - turn off email sending to prevent errors - // during unit testing - // (or sending out email by accident from tests) - mailService = (MailActionExecuter) ((ApplicationContextFactory) this.applicationContext - .getBean("OutboundSMTP")).getApplicationContext().getBean("mail"); - mailService.setTestMode(true); - - - createPerson(USER_MANAGER, USER_MANAGER + "@alfrescotesting.com", PERSON_FIRSTNAME, PERSON_LASTNAME); - createPerson(USER_ONE, USER_ONE_EMAIL, USER_ONE_FIRSTNAME, USER_ONE_LASTNAME); - createPerson(USER_TWO, USER_TWO + "@alfrescotesting.com", PERSON_FIRSTNAME, PERSON_LASTNAME); - createPerson(USER_EVE, USER_EVE + "@alfrescotesting.com", PERSON_FIRSTNAME, PERSON_LASTNAME); - createPerson(USER_NOEMAIL, null, USER_NOEMAIL, USER_NOEMAIL); - - this.authenticationComponent.setCurrentUser(USER_MANAGER); - - SiteInfo siteInfo = siteService.getSite(SITE_SHORT_NAME_INVITE); - if (siteInfo == null) - { - siteInfo = siteService.createSite("InviteSitePreset", SITE_SHORT_NAME_INVITE, "InviteSiteTitle", - "InviteSiteDescription", SiteVisibility.MODERATED); - - siteService.setMembership(SITE_SHORT_NAME_INVITE, USER_NOEMAIL, SiteModel.SITE_MANAGER); - } - - SiteInfo siteInfoRed = siteService.getSite(SITE_SHORT_NAME_RED); - if (siteInfoRed == null) - { - siteService.createSite("InviteSiteRed", SITE_SHORT_NAME_RED, "InviteSiteTitle", "InviteSiteDescription", - SiteVisibility.MODERATED); - } - SiteInfo siteInfoBlue = siteService.getSite(SITE_SHORT_NAME_BLUE); - if (siteInfoBlue == null) - { - siteService.createSite("InviteSiteBlue", SITE_SHORT_NAME_BLUE, "InviteSiteTitle", "InviteSiteDescription", - SiteVisibility.MODERATED); - } - - } - - @Override - protected void onTearDownInTransaction() throws Exception - { - // Make sure both workflow engines are enabled.and visible - - this.authenticationComponent.setSystemUserAsCurrentUser(); - - workflowAdminService.setEnabledEngines(enabledEngines); - workflowAdminService.setVisibleEngines(visibleEngines); - -// invitationServiceImpl.setSendEmails(startSendEmails); -// siteService.deleteSite(SITE_SHORT_NAME_INVITE); -// siteService.deleteSite(SITE_SHORT_NAME_RED); -// siteService.deleteSite(SITE_SHORT_NAME_BLUE); -// deletePersonByUserName(USER_ONE); -// deletePersonByUserName(USER_TWO); -// deletePersonByUserName(USER_EVE); -// deletePersonByUserName(USER_MANAGER); - super.onTearDownInTransaction(); - } - - /* - * end of setup now for some real tests - */ - - /** - * - */ - public void testConfiguration() - { - assertNotNull("Invitation service is null", invitationService); - } - - /** - * Test nominated user - new user - * - * @throws Exception - */ - public void testNominatedInvitationNewUser() throws Exception - { - Calendar calendar = Calendar.getInstance(); - calendar.add(Calendar.SECOND, -1); - Date startDate = calendar.getTime(); - - String inviteeFirstName = PERSON_FIRSTNAME; - String inviteeLastName = PERSON_LASTNAME; - String inviteeEmail = "123@alfrescotesting.com"; - String inviteeUserName = null; - Invitation.ResourceType resourceType = Invitation.ResourceType.WEB_SITE; - String resourceName = SITE_SHORT_NAME_INVITE; - String inviteeRole = SiteModel.SITE_COLLABORATOR; - String serverPath = "wibble"; - String acceptUrl = "froob"; - String rejectUrl = "marshmallow"; - - this.authenticationComponent.setCurrentUser(USER_MANAGER); - - NominatedInvitation nominatedInvitation = invitationService.inviteNominated(inviteeFirstName, inviteeLastName, - inviteeEmail, resourceType, resourceName, inviteeRole, serverPath, acceptUrl, rejectUrl); - - assertNotNull("nominated invitation is null", nominatedInvitation); - String inviteId = nominatedInvitation.getInviteId(); - assertEquals("first name wrong", inviteeFirstName, nominatedInvitation.getInviteeFirstName()); - assertEquals("last name wrong", inviteeLastName, nominatedInvitation.getInviteeLastName()); - assertEquals("email name wrong", inviteeEmail, nominatedInvitation.getInviteeEmail()); - - // Generated User Name should be returned - inviteeUserName = nominatedInvitation.getInviteeUserName(); - assertNotNull("generated user name is null", inviteeUserName); - // sentInviteDate should be set to today - { - Date sentDate = nominatedInvitation.getSentInviteDate(); - assertTrue("sentDate wrong - too early. Start Date: " +startDate +"\nSent Date: "+sentDate, sentDate.after(startDate)); - assertTrue("sentDate wrong - too lateStart Date: " +startDate +"\nSent Date: "+sentDate, sentDate.before(new Date(new Date().getTime() + 1))); - } - - assertEquals("resource type name wrong", resourceType, nominatedInvitation.getResourceType()); - assertEquals("resource name wrong", resourceName, nominatedInvitation.getResourceName()); - assertEquals("role name wrong", inviteeRole, nominatedInvitation.getRoleName()); - assertEquals("server path wrong", serverPath, nominatedInvitation.getServerPath()); - assertEquals("accept URL wrong", acceptUrl, nominatedInvitation.getAcceptUrl()); - assertEquals("reject URL wrong", rejectUrl, nominatedInvitation.getRejectUrl()); - - /** - * Now we have an invitation get it and check the details have been - * returned correctly. - */ - { - NominatedInvitation invitation = (NominatedInvitation) invitationService.getInvitation(inviteId); - - assertNotNull("invitation is null", invitation); - assertEquals("invite id wrong", inviteId, invitation.getInviteId()); - assertEquals("first name wrong", inviteeFirstName, invitation.getInviteeFirstName()); - assertEquals("last name wrong", inviteeLastName, invitation.getInviteeLastName()); - assertEquals("user name wrong", inviteeUserName, invitation.getInviteeUserName()); - assertEquals("resource type name wrong", resourceType, invitation.getResourceType()); - assertEquals("resource name wrong", resourceName, invitation.getResourceName()); - assertEquals("role name wrong", inviteeRole, invitation.getRoleName()); - assertEquals("server path wrong", serverPath, invitation.getServerPath()); - assertEquals("accept URL wrong", acceptUrl, invitation.getAcceptUrl()); - assertEquals("reject URL wrong", rejectUrl, invitation.getRejectUrl()); - - Date sentDate = invitation.getSentInviteDate(); - // sentInviteDate should be set to today - assertTrue("sentDate wrong too early", sentDate.after(startDate)); - assertTrue("sentDate wrong - too late", sentDate.before(new Date(new Date().getTime() + 1))); - } - - /** - * Check the email itself, and check it - * is as we would expect it to be - */ - { - MimeMessage msg = mailService.retrieveLastTestMessage(); - - assertEquals(1, msg.getAllRecipients().length); - assertEquals(inviteeEmail, msg.getAllRecipients()[0].toString()); - - assertEquals(1, msg.getFrom().length); - assertEquals(USER_MANAGER + "@alfrescotesting.com", msg.getFrom()[0].toString()); - - // Hasn't been sent, so no sent or received date - assertNull("Not been sent yet", msg.getSentDate()); - assertNull("Not been sent yet", msg.getReceivedDate()); - - // TODO - check some more details of the email - assertTrue((msg.getSubject().indexOf("You have been invited to join the") != -1)); - } - - /** - * Search for the new invitation - */ - List invitations = invitationService.listPendingInvitationsForResource(resourceType, resourceName); - assertTrue("invitations is empty", !invitations.isEmpty()); - - NominatedInvitation firstInvite = (NominatedInvitation) invitations.get(0); - assertEquals("invite id wrong", inviteId, firstInvite.getInviteId()); - assertEquals("first name wrong", inviteeFirstName, firstInvite.getInviteeFirstName()); - assertEquals("last name wrong", inviteeLastName, firstInvite.getInviteeLastName()); - assertEquals("user name wrong", inviteeUserName, firstInvite.getInviteeUserName()); - - /** - * Now accept the invitation - */ - NominatedInvitation acceptedInvitation = (NominatedInvitation) invitationService.accept(firstInvite - .getInviteId(), firstInvite.getTicket()); - assertEquals("invite id wrong", firstInvite.getInviteId(), acceptedInvitation.getInviteId()); - assertEquals("first name wrong", inviteeFirstName, acceptedInvitation.getInviteeFirstName()); - assertEquals("last name wrong", inviteeLastName, acceptedInvitation.getInviteeLastName()); - assertEquals("user name wrong", inviteeUserName, acceptedInvitation.getInviteeUserName()); - - List it4 = invitationService.listPendingInvitationsForResource(resourceType, resourceName); - assertTrue("invitations is not empty", it4.isEmpty()); - - /** - * Now get the invitation that we accepted - */ - NominatedInvitation acceptedInvitation2 = (NominatedInvitation) invitationService.getInvitation(firstInvite - .getInviteId()); - assertNotNull("get after accept does not return", acceptedInvitation2); - - /** - * Now verify access control list - */ - String roleName = siteService.getMembersRole(resourceName, inviteeUserName); - assertEquals("role name wrong", roleName, inviteeRole); - siteService.removeMembership(resourceName, inviteeUserName); - - - /** - * Check that system generated invitations can work as well - */ - { - Field faf = mailService.getClass().getDeclaredField("fromAddress"); - faf.setAccessible(true); - String defaultFromAddress = (String)ReflectionUtils.getField(faf, mailService); - - AuthenticationUtil.setFullyAuthenticatedUser(USER_NOEMAIL); - - // Check invitiation - NominatedInvitation nominatedInvitation2 = invitationService.inviteNominated(inviteeFirstName, inviteeLastName, - USER_TWO_EMAIL, resourceType, resourceName, inviteeRole, serverPath, acceptUrl, rejectUrl); - - assertNotNull("nominated invitation is null", nominatedInvitation2); - inviteId = nominatedInvitation.getInviteId(); - assertEquals("first name wrong", inviteeFirstName, nominatedInvitation2.getInviteeFirstName()); - assertEquals("last name wrong", inviteeLastName, nominatedInvitation2.getInviteeLastName()); - assertEquals("email name wrong", USER_TWO_EMAIL, nominatedInvitation2.getInviteeEmail()); - - // Check the email - MimeMessage msg = mailService.retrieveLastTestMessage(); - - assertEquals(1, msg.getAllRecipients().length); - assertEquals(USER_TWO_EMAIL, msg.getAllRecipients()[0].toString()); - - assertEquals(1, msg.getFrom().length); - assertEquals(defaultFromAddress, msg.getFrom()[0].toString()); - } - } - - // TODO MER START - /** - * Test nominated user - new user who rejects invitation - * - * @throws Exception - */ - public void testNominatedInvitationNewUserReject() throws Exception - { - Calendar calendar = Calendar.getInstance(); - calendar.add(Calendar.SECOND, -1); - Date startDate = calendar.getTime(); - - String inviteeFirstName = PERSON_FIRSTNAME; - String inviteeLastName = PERSON_LASTNAME; - String inviteeEmail = "123@alfrescotesting.com"; - String inviteeUserName = null; - Invitation.ResourceType resourceType = Invitation.ResourceType.WEB_SITE; - String resourceName = SITE_SHORT_NAME_INVITE; - String inviteeRole = SiteModel.SITE_COLLABORATOR; - String serverPath = "wibble"; - String acceptUrl = "froob"; - String rejectUrl = "marshmallow"; - - this.authenticationComponent.setCurrentUser(USER_MANAGER); - - NominatedInvitation nominatedInvitation = invitationService.inviteNominated(inviteeFirstName, inviteeLastName, - inviteeEmail, resourceType, resourceName, inviteeRole, serverPath, acceptUrl, rejectUrl); - - assertNotNull("nominated invitation is null", nominatedInvitation); - assertEquals("first name wrong", inviteeFirstName, nominatedInvitation.getInviteeFirstName()); - assertEquals("last name wrong", inviteeLastName, nominatedInvitation.getInviteeLastName()); - assertEquals("email name wrong", inviteeEmail, nominatedInvitation.getInviteeEmail()); - - // Generated User Name should be returned - inviteeUserName = nominatedInvitation.getInviteeUserName(); - assertNotNull("generated user name is null", inviteeUserName); - // sentInviteDate should be set to today - { - Date sentDate = nominatedInvitation.getSentInviteDate(); - assertTrue("sentDate wrong - too earlyStart Date: " +startDate +"\nSent Date: "+sentDate, sentDate.after(startDate)); - assertTrue("sentDate wrong - too lateStart Date: " +startDate +"\nSent Date: "+sentDate, sentDate.before(new Date(new Date().getTime() + 1))); - } - - /** - * Now reject the invitation - */ - NominatedInvitation rejectedInvitation = (NominatedInvitation) invitationService.reject(nominatedInvitation - .getInviteId(), "dont want it"); - assertEquals("invite id wrong", nominatedInvitation.getInviteId(), rejectedInvitation.getInviteId()); - assertEquals("first name wrong", inviteeFirstName, rejectedInvitation.getInviteeFirstName()); - assertEquals("last name wrong", inviteeLastName, rejectedInvitation.getInviteeLastName()); - assertEquals("user name wrong", inviteeUserName, rejectedInvitation.getInviteeUserName()); - - List it4 = invitationService.listPendingInvitationsForResource(resourceType, resourceName); - assertTrue("invitations is not empty", it4.isEmpty()); - - /** - * Now verify access control list inviteeUserName should not exist - */ - String roleName = siteService.getMembersRole(resourceName, inviteeUserName); - if (roleName != null) - { - fail("role has been set for a rejected user"); - } - - /** - * Now verify that the generated user has been removed - */ - if (personService.personExists(inviteeUserName)) - { - fail("generated user has not been cleaned up"); - } - } - - // TODO MER END - - /** - * Test nominated user - new user Creates two separate users with two the - * same email address. - * - * @throws Exception - */ - public void testNominatedInvitationNewUserSameEmails() throws Exception - { - String inviteeAFirstName = "John"; - String inviteeALastName = "Smith"; - - String inviteeBFirstName = "Jane"; - String inviteeBLastName = "Smith"; - - String inviteeEmail = "123@alfrescotesting.com"; - String inviteeAUserName = null; - String inviteeBUserName = null; - - Invitation.ResourceType resourceType = Invitation.ResourceType.WEB_SITE; - String resourceName = SITE_SHORT_NAME_INVITE; - String inviteeRole = SiteModel.SITE_COLLABORATOR; - String serverPath = "wibble"; - String acceptUrl = "froob"; - String rejectUrl = "marshmallow"; - - this.authenticationComponent.setCurrentUser(USER_MANAGER); - - NominatedInvitation nominatedInvitationA = invitationService.inviteNominated(inviteeAFirstName, - inviteeALastName, inviteeEmail, resourceType, resourceName, inviteeRole, serverPath, acceptUrl, - rejectUrl); - - assertNotNull("nominated invitation is null", nominatedInvitationA); - String inviteAId = nominatedInvitationA.getInviteId(); - assertEquals("first name wrong", inviteeAFirstName, nominatedInvitationA.getInviteeFirstName()); - assertEquals("last name wrong", inviteeALastName, nominatedInvitationA.getInviteeLastName()); - assertEquals("email name wrong", inviteeEmail, nominatedInvitationA.getInviteeEmail()); - - // Generated User Name should be returned - inviteeAUserName = nominatedInvitationA.getInviteeUserName(); - assertNotNull("generated user name is null", inviteeAUserName); - - NominatedInvitation nominatedInvitationB = invitationService.inviteNominated(inviteeBFirstName, - inviteeBLastName, inviteeEmail, resourceType, resourceName, inviteeRole, serverPath, acceptUrl, - rejectUrl); - - assertNotNull("nominated invitation is null", nominatedInvitationB); - String inviteBId = nominatedInvitationB.getInviteId(); - assertEquals("first name wrong", inviteeBFirstName, nominatedInvitationB.getInviteeFirstName()); - assertEquals("last name wrong", inviteeBLastName, nominatedInvitationB.getInviteeLastName()); - assertEquals("email name wrong", inviteeEmail, nominatedInvitationB.getInviteeEmail()); - - // Generated User Name should be returned - inviteeBUserName = nominatedInvitationB.getInviteeUserName(); - assertNotNull("generated user name is null", inviteeBUserName); - assertFalse("generated user names are the same", inviteeAUserName.equals(inviteeBUserName)); - - /** - * Now accept the invitation - */ - NominatedInvitation acceptedInvitationA = (NominatedInvitation) invitationService.accept(inviteAId, - nominatedInvitationA.getTicket()); - assertEquals("invite id wrong", inviteAId, acceptedInvitationA.getInviteId()); - assertEquals("first name wrong", inviteeAFirstName, acceptedInvitationA.getInviteeFirstName()); - assertEquals("last name wrong", inviteeALastName, acceptedInvitationA.getInviteeLastName()); - assertEquals("user name wrong", inviteeAUserName, acceptedInvitationA.getInviteeUserName()); - - NominatedInvitation acceptedInvitationB = (NominatedInvitation) invitationService.accept(inviteBId, - nominatedInvitationB.getTicket()); - assertEquals("invite id wrong", inviteBId, acceptedInvitationB.getInviteId()); - assertEquals("first name wrong", inviteeBFirstName, acceptedInvitationB.getInviteeFirstName()); - assertEquals("last name wrong", inviteeBLastName, acceptedInvitationB.getInviteeLastName()); - assertEquals("user name wrong", inviteeBUserName, acceptedInvitationB.getInviteeUserName()); - - /** - * Now verify access control list - */ - String roleNameA = siteService.getMembersRole(resourceName, inviteeAUserName); - assertEquals("role name wrong", roleNameA, inviteeRole); - String roleNameB = siteService.getMembersRole(resourceName, inviteeBUserName); - assertEquals("role name wrong", roleNameB, inviteeRole); - siteService.removeMembership(resourceName, inviteeAUserName); - siteService.removeMembership(resourceName, inviteeBUserName); - } - - /** - * Test nominated user - new user with whitespace in name. Related to - * ETHREEOH-3030. - */ - public void testNominatedInvitationNewUserWhitespace() throws Exception - { - String inviteeFirstName = PERSON_FIRSTNAME_SPACES; - String inviteeLastName = PERSON_LASTNAME_SPACES; - String resourceName = SITE_SHORT_NAME_INVITE; - String inviteeEmail = "123@alfrescotesting.com"; - Invitation.ResourceType resourceType = Invitation.ResourceType.WEB_SITE; - String inviteeRole = SiteModel.SITE_COLLABORATOR; - String serverPath = "wibble"; - String acceptUrl = "froob"; - String rejectUrl = "marshmallow"; - String expectedUserName = (inviteeFirstName + "_" + inviteeLastName).toLowerCase(); - expectedUserName = expectedUserName.replaceAll("\\s+", "_"); - authenticationComponent.setCurrentUser(USER_MANAGER); - - NominatedInvitation nominatedInvitation = invitationService.inviteNominated(inviteeFirstName, inviteeLastName, - inviteeEmail, resourceType, resourceName, inviteeRole, serverPath, acceptUrl, rejectUrl); - - assertNotNull("nominated invitation is null", nominatedInvitation); - assertEquals("Wrong username!", expectedUserName, nominatedInvitation.getInviteeUserName()); - - String inviteId = nominatedInvitation.getInviteId(); - - // Now we have an invitation get it and check the details have been - // returned correctly. - NominatedInvitation invitation = (NominatedInvitation) invitationService.getInvitation(inviteId); - assertNotNull("invitation is null", invitation); - assertEquals("first name wrong", inviteeFirstName, invitation.getInviteeFirstName()); - assertEquals("last name wrong", inviteeLastName, invitation.getInviteeLastName()); - assertEquals("user name wrong", expectedUserName, invitation.getInviteeUserName()); - - // Now accept the invitation - NominatedInvitation acceptedInvitation = (NominatedInvitation) invitationService.accept(invitation - .getInviteId(), invitation.getTicket()); - - assertEquals("first name wrong", inviteeFirstName, acceptedInvitation.getInviteeFirstName()); - assertEquals("last name wrong", inviteeLastName, acceptedInvitation.getInviteeLastName()); - assertEquals("user name wrong", expectedUserName, acceptedInvitation.getInviteeUserName()); - - // Now verify access control list - String roleName = siteService.getMembersRole(resourceName, expectedUserName); - assertEquals("role name wrong", roleName, inviteeRole); - siteService.removeMembership(resourceName, expectedUserName); - } - - /** - * Create a Nominated Invitation (for existing user, USER_ONE) read it. - * search for it cancel it search for it again (and fail to find it) Create - * a Nominated Invitation read it. search for it reject it Create a - * Nominated Invitation read it. accept it - */ - public void testNominatedInvitationExistingUser() throws Exception - { - String inviteeUserName = USER_ONE; - String inviteeEmail = USER_ONE_EMAIL; - String inviteeFirstName = USER_ONE_FIRSTNAME; - String inviteeLastName = USER_ONE_LASTNAME; - - Invitation.ResourceType resourceType = Invitation.ResourceType.WEB_SITE; - String resourceName = SITE_SHORT_NAME_INVITE; - String inviteeRole = SiteModel.SITE_COLLABORATOR; - String serverPath = "wibble"; - String acceptUrl = "froob"; - String rejectUrl = "marshmallow"; - - authenticationComponent.setCurrentUser(USER_MANAGER); - - NominatedInvitation nominatedInvitation = invitationService.inviteNominated(inviteeUserName, resourceType, - resourceName, inviteeRole, serverPath, acceptUrl, rejectUrl); - - assertNotNull("nominated invitation is null", nominatedInvitation); - String inviteId = nominatedInvitation.getInviteId(); - assertEquals("user name wrong", inviteeUserName, nominatedInvitation.getInviteeUserName()); - assertEquals("resource type name wrong", resourceType, nominatedInvitation.getResourceType()); - assertEquals("resource name wrong", resourceName, nominatedInvitation.getResourceName()); - assertEquals("role name wrong", inviteeRole, nominatedInvitation.getRoleName()); - assertEquals("server path wrong", serverPath, nominatedInvitation.getServerPath()); - assertEquals("accept URL wrong", acceptUrl, nominatedInvitation.getAcceptUrl()); - assertEquals("reject URL wrong", rejectUrl, nominatedInvitation.getRejectUrl()); - - // These values should be read from the person record - assertEquals("first name wrong", inviteeFirstName, nominatedInvitation.getInviteeFirstName()); - assertEquals("last name wrong", inviteeLastName, nominatedInvitation.getInviteeLastName()); - assertEquals("email name wrong", inviteeEmail, nominatedInvitation.getInviteeEmail()); - - /** - * Now we have an invitation get it and check the details have been - * returned correctly. - */ - NominatedInvitation invitation = (NominatedInvitation) invitationService.getInvitation(inviteId); - - assertNotNull("invitation is null", invitation); - assertEquals("invite id wrong", inviteId, invitation.getInviteId()); - assertEquals("user name wrong", inviteeUserName, nominatedInvitation.getInviteeUserName()); - assertEquals("resource type name wrong", resourceType, invitation.getResourceType()); - assertEquals("resource name wrong", resourceName, invitation.getResourceName()); - assertEquals("role name wrong", inviteeRole, invitation.getRoleName()); - assertEquals("server path wrong", serverPath, invitation.getServerPath()); - assertEquals("accept URL wrong", acceptUrl, invitation.getAcceptUrl()); - assertEquals("reject URL wrong", rejectUrl, invitation.getRejectUrl()); - - // These values should have been read from the DB - assertEquals("first name wrong", inviteeFirstName, invitation.getInviteeFirstName()); - assertEquals("last name wrong", inviteeLastName, invitation.getInviteeLastName()); - assertEquals("email name wrong", inviteeEmail, invitation.getInviteeEmail()); - - /** - * Search for the new invitation - */ - List invitations = invitationService.listPendingInvitationsForResource(resourceType, resourceName); - assertTrue("invitations is empty", !invitations.isEmpty()); - - NominatedInvitation firstInvite = (NominatedInvitation) invitations.get(0); - assertEquals("invite id wrong", inviteId, firstInvite.getInviteId()); - assertEquals("first name wrong", inviteeFirstName, firstInvite.getInviteeFirstName()); - assertEquals("last name wrong", inviteeLastName, firstInvite.getInviteeLastName()); - assertEquals("user name wrong", inviteeUserName, firstInvite.getInviteeUserName()); - - /** - * Now cancel the invitation - */ - NominatedInvitation canceledInvitation = (NominatedInvitation) invitationService.cancel(inviteId); - assertEquals("invite id wrong", inviteId, canceledInvitation.getInviteId()); - assertEquals("first name wrong", inviteeFirstName, canceledInvitation.getInviteeFirstName()); - assertEquals("last name wrong", inviteeLastName, canceledInvitation.getInviteeLastName()); - assertEquals("user name wrong", inviteeUserName, canceledInvitation.getInviteeUserName()); - - /** - * Do the query again - should no longer find anything - */ - List it2 = invitationService.listPendingInvitationsForResource(resourceType, resourceName); - assertTrue("invitations is not empty", it2.isEmpty()); - - /** - * Now invite and reject - */ - NominatedInvitation secondInvite = invitationService.inviteNominated(inviteeUserName, resourceType, - resourceName, inviteeRole, serverPath, acceptUrl, rejectUrl); - - NominatedInvitation rejectedInvitation = (NominatedInvitation) invitationService.cancel(secondInvite - .getInviteId()); - assertEquals("invite id wrong", secondInvite.getInviteId(), rejectedInvitation.getInviteId()); - assertEquals("user name wrong", inviteeUserName, rejectedInvitation.getInviteeUserName()); - - List it3 = invitationService.listPendingInvitationsForResource(resourceType, resourceName); - assertTrue("invitations is not empty", it3.isEmpty()); - - /** - * Now invite and accept - */ - NominatedInvitation thirdInvite = invitationService.inviteNominated(inviteeUserName, resourceType, - resourceName, inviteeRole, serverPath, acceptUrl, rejectUrl); - - NominatedInvitation acceptedInvitation = (NominatedInvitation) invitationService.accept(thirdInvite - .getInviteId(), thirdInvite.getTicket()); - assertEquals("invite id wrong", thirdInvite.getInviteId(), acceptedInvitation.getInviteId()); - assertEquals("first name wrong", inviteeFirstName, acceptedInvitation.getInviteeFirstName()); - assertEquals("last name wrong", inviteeLastName, acceptedInvitation.getInviteeLastName()); - assertEquals("user name wrong", inviteeUserName, acceptedInvitation.getInviteeUserName()); - - List it4 = invitationService.listPendingInvitationsForResource(resourceType, resourceName); - assertTrue("invitations is not empty", it4.isEmpty()); - - /** - * Now verify access control list - */ - String roleName = siteService.getMembersRole(resourceName, inviteeUserName); - assertEquals("role name wrong", inviteeRole, roleName); - siteService.removeMembership(resourceName, inviteeUserName); - } - - /** - * Create a moderated invitation Get it Search for it Cancel it Create a - * moderated invitation Reject the invitation Create a moderated invitation - * Approve the invitation - */ - public void testModeratedInvitation() - { - String inviteeUserName = USER_TWO; - Invitation.ResourceType resourceType = Invitation.ResourceType.WEB_SITE; - String resourceName = SITE_SHORT_NAME_INVITE; - String inviteeRole = SiteModel.SITE_COLLABORATOR; - String comments = "please sir, let me in!"; - - this.authenticationComponent.setCurrentUser(USER_TWO); - ModeratedInvitation invitation = invitationService.inviteModerated(comments, inviteeUserName, resourceType, - resourceName, inviteeRole); - - assertNotNull("moderated invitation is null", invitation); - String inviteId = invitation.getInviteId(); - assertEquals("user name wrong", inviteeUserName, invitation.getInviteeUserName()); - assertEquals("role name wrong", inviteeRole, invitation.getRoleName()); - assertEquals("comments", comments, invitation.getInviteeComments()); - assertEquals("resource type name wrong", resourceType, invitation.getResourceType()); - assertEquals("resource name wrong", resourceName, invitation.getResourceName()); - - /** - * Now we have an invitation get it and check the details have been - * returned correctly. - */ - ModeratedInvitation mi2 = (ModeratedInvitation) invitationService.getInvitation(inviteId); - assertEquals("invite id", inviteId, mi2.getInviteId()); - assertEquals("user name wrong", inviteeUserName, mi2.getInviteeUserName()); - assertEquals("role name wrong", inviteeRole, mi2.getRoleName()); - assertEquals("comments", comments, mi2.getInviteeComments()); - assertEquals("resource type name wrong", resourceType, mi2.getResourceType()); - assertEquals("resource name wrong", resourceName, mi2.getResourceName()); - - /** - * Search for the new invitation - */ - List invitations = invitationService.listPendingInvitationsForResource(resourceType, resourceName); - assertTrue("invitations is empty", !invitations.isEmpty()); - - ModeratedInvitation firstInvite = (ModeratedInvitation) invitations.get(0); - assertEquals("invite id wrong", inviteId, firstInvite.getInviteId()); - - /** - * Cancel the invitation - */ - ModeratedInvitation canceledInvitation = (ModeratedInvitation) invitationService.cancel(inviteId); - assertEquals("invite id wrong", inviteId, canceledInvitation.getInviteId()); - assertEquals("comments wrong", comments, canceledInvitation.getInviteeComments()); - - /** - * Should now be no invitation - */ - List inv2 = invitationService.listPendingInvitationsForResource(resourceType, resourceName); - assertTrue("After cancel invitations is not empty", inv2.isEmpty()); - - /** - * New invitation - */ - this.authenticationComponent.setCurrentUser(USER_TWO); - ModeratedInvitation invite2 = invitationService.inviteModerated(comments, inviteeUserName, resourceType, - resourceName, inviteeRole); - - String secondInvite = invite2.getInviteId(); - - this.authenticationComponent.setCurrentUser(USER_MANAGER); - invitationService.reject(secondInvite, "This is a test reject"); - - /** - * New invitation - */ - this.authenticationComponent.setCurrentUser(USER_TWO); - ModeratedInvitation invite3 = invitationService.inviteModerated(comments, inviteeUserName, resourceType, - resourceName, inviteeRole); - - String thirdInvite = invite3.getInviteId(); - - this.authenticationComponent.setCurrentUser(USER_MANAGER); - invitationService.approve(thirdInvite, "Welcome in"); - - /** - * Now verify access control list - */ - String roleName = siteService.getMembersRole(resourceName, inviteeUserName); - assertEquals("role name wrong", inviteeRole, roleName); - siteService.removeMembership(resourceName, inviteeUserName); - - } - - /** - * Test the approval of a moderated invitation - */ - public void testModeratedApprove() - { - String inviteeUserName = USER_TWO; - Invitation.ResourceType resourceType = Invitation.ResourceType.WEB_SITE; - String resourceName = SITE_SHORT_NAME_INVITE; - String inviteeRole = SiteModel.SITE_COLLABORATOR; - String comments = "please sir, let me in!"; - - /** - * New invitation from User TWO - */ - this.authenticationComponent.setCurrentUser(USER_TWO); - ModeratedInvitation invitation = invitationService.inviteModerated(comments, inviteeUserName, resourceType, - resourceName, inviteeRole); - - String invitationId = invitation.getInviteId(); - - /** - * Negative test Attempt to approve without the necessary role - */ - try - { - invitationService.approve(invitationId, "No Way Hosea!"); - assertTrue("excetion not thrown", false); - - } - catch (Exception e) - { - // An exception should have been thrown - e.printStackTrace(); - System.out.println(e.toString()); - } - - /** - * Approve the invitation - */ - this.authenticationComponent.setCurrentUser(USER_MANAGER); - invitationService.approve(invitationId, "Come on in"); - - /** - * Now verify access control list contains user two - */ - String roleName = siteService.getMembersRole(resourceName, inviteeUserName); - assertEquals("role name wrong", inviteeRole, roleName); - - /** - * Negative test attempt to approve an invitation that has aready been - * approved - */ - try - { - invitationService.approve(invitationId, "Have I not already done this?"); - assertTrue("duplicate approve excetion not thrown", false); - } - catch (Exception e) - { - // An exception should have been thrown - e.printStackTrace(); - System.out.println(e.toString()); - } - /** - * Negative test User is already a member of the site - */ - siteService.removeMembership(resourceName, inviteeUserName); - } - - /** - * Tests of Moderated Reject - */ - public void testModeratedReject() - { - String inviteeUserName = USER_TWO; - Invitation.ResourceType resourceType = Invitation.ResourceType.WEB_SITE; - String resourceName = SITE_SHORT_NAME_INVITE; - String inviteeRole = SiteModel.SITE_COLLABORATOR; - String comments = "please sir, let me in!"; - - /** - * New invitation from User TWO - */ - this.authenticationComponent.setCurrentUser(USER_TWO); - ModeratedInvitation invitation = invitationService.inviteModerated(comments, inviteeUserName, resourceType, - resourceName, inviteeRole); - - String invitationId = invitation.getInviteId(); - - /** - * Negative test Attempt to reject without the necessary role - */ - try - { - invitationService.reject(invitationId, "No Way Hosea!"); - assertTrue("excetion not thrown", false); - - } - catch (Exception e) - { - // An exception should have been thrown - e.printStackTrace(); - System.out.println(e.toString()); - } - - /** - * Reject the invitation - */ - this.authenticationComponent.setCurrentUser(USER_MANAGER); - invitationService.reject(invitationId, "Go away!"); - - /** - * Negative test attempt to approve an invitation that has been rejected - */ - try - { - invitationService.approve(invitationId, "Have I not rejected this?"); - assertTrue("rejected invitation not working", false); - } - catch (Exception e) - { - // An exception should have been thrown - e.printStackTrace(); - System.out.println(e.toString()); - } - } - - /** - * Test search invitation - */ - public void testSearchInvitation() - { - /** - * Make up a tree of invitations and then search Resource, User, - * Workflow 1) RED, One, Moderated 2) RED, One, Nominated 3) BLUE, One, - * Nominated 4) RED, Two, Moderated - */ - Invitation.ResourceType resourceType = Invitation.ResourceType.WEB_SITE; - String inviteeRole = SiteModel.SITE_COLLABORATOR; - String comments = "please sir, let me in!"; - String serverPath = "wibble"; - String acceptUrl = "froob"; - String rejectUrl = "marshmallow"; - - this.authenticationComponent.setCurrentUser(USER_MANAGER); - invitationService.inviteModerated(comments, USER_ONE, resourceType, SITE_SHORT_NAME_RED, inviteeRole); - - invitationService.inviteNominated(USER_ONE, resourceType, SITE_SHORT_NAME_RED, inviteeRole, serverPath, - acceptUrl, rejectUrl); - - NominatedInvitation invitationThree = invitationService.inviteNominated(USER_ONE, resourceType, - SITE_SHORT_NAME_BLUE, inviteeRole, serverPath, acceptUrl, rejectUrl); - String threeId = invitationThree.getInviteId(); - - invitationService.inviteModerated(comments, USER_TWO, resourceType, SITE_SHORT_NAME_RED, inviteeRole); - - /** - * Search for invitations for BLUE - */ - List resOne = invitationService.listPendingInvitationsForResource(ResourceType.WEB_SITE, - SITE_SHORT_NAME_BLUE); - assertEquals("blue invites not 1", 1, resOne.size()); - assertEquals("blue id wrong", threeId, resOne.get(0).getInviteId()); - - /** - * Search for invitations for RED - */ - List resTwo = invitationService.listPendingInvitationsForResource(ResourceType.WEB_SITE, - SITE_SHORT_NAME_RED); - assertEquals("red invites not 3", 3, resTwo.size()); - - /** - * Search for invitations for USER_ONE - */ - List resThree = invitationService.listPendingInvitationsForInvitee(USER_ONE); - assertEquals("user one does not have 3 invitations", 3, resThree.size()); - - /** - * Search for invitations for USER_TWO - */ - List resFour = invitationService.listPendingInvitationsForInvitee(USER_TWO); - assertEquals("user two does not have 1 invitations", 1, resFour.size()); - - /** - * Search for user1's nominated invitations - */ - InvitationSearchCriteriaImpl crit1 = new InvitationSearchCriteriaImpl(); - crit1.setInvitee(USER_ONE); - crit1.setInvitationType(InvitationSearchCriteria.InvitationType.NOMINATED); - - List resFive = invitationService.searchInvitation(crit1); - assertEquals("user one does not have 2 nominated", 2, resFive.size()); - - /** - * Search with an empty criteria - should find all open invitations - */ - InvitationSearchCriteria crit2 = new InvitationSearchCriteriaImpl(); - invitationService.searchInvitation(crit2); - assertTrue("search everything returned 0 elements", resFive.size() > 0); - - InvitationSearchCriteriaImpl crit3 = new InvitationSearchCriteriaImpl(); - crit3.setInviter(USER_MANAGER); - crit3.setInvitationType(InvitationSearchCriteria.InvitationType.NOMINATED); - - List res3 = invitationService.searchInvitation(crit3); - assertEquals("user one does not have 2 nominated", 2, res3.size()); - - } - - public void disabled_test100Invites() throws Exception - { - Invitation.ResourceType resourceType = Invitation.ResourceType.WEB_SITE; - String resourceName = SITE_SHORT_NAME_INVITE; - String inviteeRole = SiteModel.SITE_COLLABORATOR; - String serverPath = "wibble"; - String acceptUrl = "froob"; - String rejectUrl = "marshmallow"; - - authenticationComponent.setCurrentUser(USER_MANAGER); - - // Create 1000 invites - for (int i = 0; i < 1000; i++) - { - invitationService.inviteNominated(USER_ONE, resourceType, resourceName, inviteeRole, serverPath, acceptUrl, rejectUrl); - } - - // Invite USER_TWO - NominatedInvitation invite = invitationService.inviteNominated(USER_TWO, resourceType, resourceName, inviteeRole, serverPath, acceptUrl, rejectUrl); - - InvitationSearchCriteriaImpl query = new InvitationSearchCriteriaImpl(); - query.setInvitee(USER_TWO); - - long start = System.currentTimeMillis(); - List results = invitationService.searchInvitation(query); - long end= System.currentTimeMillis(); - System.out.println("Invitation Search took " + (end - start) + "ms."); - - assertEquals(1, results.size()); - assertEquals(invite.getInviteId(), results.get(0).getInviteId()); - this.setComplete(); - this.endTransaction(); - - } - - public void testGetInvitation() - { - try - { - /** - * Get an invitation that does not exist. - */ - invitationService.getInvitation("jbpm$99999999"); - fail("should have thrown an exception"); - } - catch (Exception e) - { - // should have gone here - } - } - - private void createPerson(String userName, String emailAddress, String firstName, String lastName) - { - // if user with given user name doesn't already exist then create user - if (this.authenticationService.authenticationExists(userName) == false) - { - // create user - this.authenticationService.createAuthentication(userName, "password".toCharArray()); - } - - // if person node with given user name doesn't already exist then create - // person - if (this.personService.personExists(userName) == false) - { - // create person properties - PropertyMap personProps = new PropertyMap(); - personProps.put(ContentModel.PROP_USERNAME, userName); - personProps.put(ContentModel.PROP_FIRSTNAME, firstName); - personProps.put(ContentModel.PROP_LASTNAME, lastName); - personProps.put(ContentModel.PROP_EMAIL, emailAddress); - personProps.put(ContentModel.PROP_JOBTITLE, PERSON_JOBTITLE); - personProps.put(ContentModel.PROP_ORGANIZATION, PERSON_ORG); - - // create person node for user - this.personService.createPerson(personProps); - } - } - - private void deletePersonByUserName(String userName) - { - // delete person node associated with given user name - // if one exists - if (this.personService.personExists(userName)) - { - this.personService.deletePerson(userName); - } - } -} +/* + * 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.invitation; + +import java.lang.reflect.Field; +import java.util.Calendar; +import java.util.Collection; +import java.util.Date; +import java.util.List; + +import javax.mail.internet.MimeMessage; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.action.executer.MailActionExecuter; +import org.alfresco.repo.management.subsystems.ApplicationContextFactory; +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.site.SiteModel; +import org.alfresco.repo.workflow.WorkflowAdminServiceImpl; +import org.alfresco.service.cmr.invitation.Invitation; +import org.alfresco.service.cmr.invitation.Invitation.ResourceType; +import org.alfresco.service.cmr.invitation.InvitationSearchCriteria; +import org.alfresco.service.cmr.invitation.InvitationService; +import org.alfresco.service.cmr.invitation.ModeratedInvitation; +import org.alfresco.service.cmr.invitation.NominatedInvitation; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.cmr.site.SiteInfo; +import org.alfresco.service.cmr.site.SiteService; +import org.alfresco.service.cmr.site.SiteVisibility; +import org.alfresco.util.BaseAlfrescoSpringTest; +import org.alfresco.util.PropertyMap; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.util.ReflectionUtils; + +/** + * Unit tests of Invitation Service + */ +public abstract class AbstractInvitationServiceImplTest extends BaseAlfrescoSpringTest +{ + private static final Log logger = LogFactory.getLog(AbstractInvitationServiceImplTest.class); + + private SiteService siteService; + private PersonService personService; + protected InvitationService invitationService; + private MailActionExecuter mailService; + private boolean startSendEmails; + protected InvitationServiceImpl invitationServiceImpl; + protected WorkflowAdminServiceImpl workflowAdminService; + + protected final static String SITE_SHORT_NAME_INVITE = "InvitationTest"; + protected final static String SITE_SHORT_NAME_RED = "InvitationTestRed"; + protected final static String SITE_SHORT_NAME_BLUE = "InvitationTestBlue"; + public final static String PERSON_FIRSTNAME = "InvitationFirstName123"; + public final static String PERSON_FIRSTNAME_SPACES = "Invitation First\tName\n1\r2\r\n3"; + public final static String PERSON_LASTNAME = "InvitationLastName123"; + public final static String PERSON_LASTNAME_SPACES = "Invitation Last\tName\n1\r2\r\n3"; + public final static String PERSON_JOBTITLE = "JobTitle123"; + public final static String PERSON_ORG = "Organisation123"; + + public final static String USER_MANAGER = "InvitationServiceManagerOne"; + public final static String USER_ONE = "InvitationServiceAlice"; + public final static String USER_TWO = "InvitationServiceBob"; + public final static String USER_EVE = "InvitationServiceEve"; + public final static String USER_NOEMAIL = "InvitationServiceNoEmail"; + public final static String USER_ONE_FIRSTNAME = "One"; + public final static String USER_ONE_LASTNAME = "Test"; + public final static String USER_ONE_EMAIL = USER_ONE + "@alfrescotesting.com"; + public final static String USER_TWO_EMAIL = USER_TWO + "@alfrescotesting.com"; + + private Collection enabledEngines; + private Collection visibleEngines; + /** + * Called during the transaction setup + */ + @SuppressWarnings("deprecation") + @Override + protected void onSetUpInTransaction() throws Exception + { + super.onSetUpInTransaction(); + this.invitationService = (InvitationService) this.applicationContext.getBean("InvitationService"); + this.siteService = (SiteService) this.applicationContext.getBean("SiteService"); + this.personService = (PersonService) this.applicationContext.getBean("PersonService"); + this.authenticationComponent = (AuthenticationComponent) this.applicationContext + .getBean("authenticationComponent"); + this.invitationServiceImpl = (InvitationServiceImpl) applicationContext.getBean("invitationService"); + this.workflowAdminService = (WorkflowAdminServiceImpl)applicationContext.getBean(WorkflowAdminServiceImpl.NAME); + + this.startSendEmails = invitationServiceImpl.isSendEmails(); + + this.enabledEngines = workflowAdminService.getEnabledEngines(); + this.visibleEngines = workflowAdminService.getVisibleEngines(); + + invitationServiceImpl.setSendEmails(true); + + // TODO MER 20/11/2009 Bodge - turn off email sending to prevent errors + // during unit testing + // (or sending out email by accident from tests) + mailService = (MailActionExecuter) ((ApplicationContextFactory) this.applicationContext + .getBean("OutboundSMTP")).getApplicationContext().getBean("mail"); + mailService.setTestMode(true); + + + createPerson(USER_MANAGER, USER_MANAGER + "@alfrescotesting.com", PERSON_FIRSTNAME, PERSON_LASTNAME); + createPerson(USER_ONE, USER_ONE_EMAIL, USER_ONE_FIRSTNAME, USER_ONE_LASTNAME); + createPerson(USER_TWO, USER_TWO + "@alfrescotesting.com", PERSON_FIRSTNAME, PERSON_LASTNAME); + createPerson(USER_EVE, USER_EVE + "@alfrescotesting.com", PERSON_FIRSTNAME, PERSON_LASTNAME); + createPerson(USER_NOEMAIL, null, USER_NOEMAIL, USER_NOEMAIL); + + this.authenticationComponent.setCurrentUser(USER_MANAGER); + + SiteInfo siteInfo = siteService.getSite(SITE_SHORT_NAME_INVITE); + if (siteInfo == null) + { + siteInfo = siteService.createSite("InviteSitePreset", SITE_SHORT_NAME_INVITE, "InviteSiteTitle", + "InviteSiteDescription", SiteVisibility.MODERATED); + + siteService.setMembership(SITE_SHORT_NAME_INVITE, USER_NOEMAIL, SiteModel.SITE_MANAGER); + } + + SiteInfo siteInfoRed = siteService.getSite(SITE_SHORT_NAME_RED); + if (siteInfoRed == null) + { + siteService.createSite("InviteSiteRed", SITE_SHORT_NAME_RED, "InviteSiteTitle", "InviteSiteDescription", + SiteVisibility.MODERATED); + } + SiteInfo siteInfoBlue = siteService.getSite(SITE_SHORT_NAME_BLUE); + if (siteInfoBlue == null) + { + siteService.createSite("InviteSiteBlue", SITE_SHORT_NAME_BLUE, "InviteSiteTitle", "InviteSiteDescription", + SiteVisibility.MODERATED); + } + + } + + @Override + protected void onTearDownInTransaction() throws Exception + { + // Make sure both workflow engines are enabled.and visible + + this.authenticationComponent.setSystemUserAsCurrentUser(); + + workflowAdminService.setEnabledEngines(enabledEngines); + workflowAdminService.setVisibleEngines(visibleEngines); + +// invitationServiceImpl.setSendEmails(startSendEmails); +// siteService.deleteSite(SITE_SHORT_NAME_INVITE); +// siteService.deleteSite(SITE_SHORT_NAME_RED); +// siteService.deleteSite(SITE_SHORT_NAME_BLUE); +// deletePersonByUserName(USER_ONE); +// deletePersonByUserName(USER_TWO); +// deletePersonByUserName(USER_EVE); +// deletePersonByUserName(USER_MANAGER); + super.onTearDownInTransaction(); + } + + /* + * end of setup now for some real tests + */ + + /** + * + */ + public void testConfiguration() + { + assertNotNull("Invitation service is null", invitationService); + } + + /** + * Test nominated user - new user + * + * @throws Exception + */ + public void testNominatedInvitationNewUser() throws Exception + { + Calendar calendar = Calendar.getInstance(); + calendar.add(Calendar.SECOND, -1); + Date startDate = calendar.getTime(); + + String inviteeFirstName = PERSON_FIRSTNAME; + String inviteeLastName = PERSON_LASTNAME; + String inviteeEmail = "123@alfrescotesting.com"; + String inviteeUserName = null; + Invitation.ResourceType resourceType = Invitation.ResourceType.WEB_SITE; + String resourceName = SITE_SHORT_NAME_INVITE; + String inviteeRole = SiteModel.SITE_COLLABORATOR; + String serverPath = "wibble"; + String acceptUrl = "froob"; + String rejectUrl = "marshmallow"; + + this.authenticationComponent.setCurrentUser(USER_MANAGER); + + NominatedInvitation nominatedInvitation = invitationService.inviteNominated(inviteeFirstName, inviteeLastName, + inviteeEmail, resourceType, resourceName, inviteeRole, serverPath, acceptUrl, rejectUrl); + + assertNotNull("nominated invitation is null", nominatedInvitation); + String inviteId = nominatedInvitation.getInviteId(); + assertEquals("first name wrong", inviteeFirstName, nominatedInvitation.getInviteeFirstName()); + assertEquals("last name wrong", inviteeLastName, nominatedInvitation.getInviteeLastName()); + assertEquals("email name wrong", inviteeEmail, nominatedInvitation.getInviteeEmail()); + + // Generated User Name should be returned + inviteeUserName = nominatedInvitation.getInviteeUserName(); + assertNotNull("generated user name is null", inviteeUserName); + // sentInviteDate should be set to today + { + Date sentDate = nominatedInvitation.getSentInviteDate(); + assertTrue("sentDate wrong - too early. Start Date: " +startDate +"\nSent Date: "+sentDate, sentDate.after(startDate)); + assertTrue("sentDate wrong - too lateStart Date: " +startDate +"\nSent Date: "+sentDate, sentDate.before(new Date(new Date().getTime() + 1))); + } + + assertEquals("resource type name wrong", resourceType, nominatedInvitation.getResourceType()); + assertEquals("resource name wrong", resourceName, nominatedInvitation.getResourceName()); + assertEquals("role name wrong", inviteeRole, nominatedInvitation.getRoleName()); + assertEquals("server path wrong", serverPath, nominatedInvitation.getServerPath()); + assertEquals("accept URL wrong", acceptUrl, nominatedInvitation.getAcceptUrl()); + assertEquals("reject URL wrong", rejectUrl, nominatedInvitation.getRejectUrl()); + + /** + * Now we have an invitation get it and check the details have been + * returned correctly. + */ + { + NominatedInvitation invitation = (NominatedInvitation) invitationService.getInvitation(inviteId); + + assertNotNull("invitation is null", invitation); + assertEquals("invite id wrong", inviteId, invitation.getInviteId()); + assertEquals("first name wrong", inviteeFirstName, invitation.getInviteeFirstName()); + assertEquals("last name wrong", inviteeLastName, invitation.getInviteeLastName()); + assertEquals("user name wrong", inviteeUserName, invitation.getInviteeUserName()); + assertEquals("resource type name wrong", resourceType, invitation.getResourceType()); + assertEquals("resource name wrong", resourceName, invitation.getResourceName()); + assertEquals("role name wrong", inviteeRole, invitation.getRoleName()); + assertEquals("server path wrong", serverPath, invitation.getServerPath()); + assertEquals("accept URL wrong", acceptUrl, invitation.getAcceptUrl()); + assertEquals("reject URL wrong", rejectUrl, invitation.getRejectUrl()); + + Date sentDate = invitation.getSentInviteDate(); + // sentInviteDate should be set to today + assertTrue("sentDate wrong too early", sentDate.after(startDate)); + assertTrue("sentDate wrong - too late", sentDate.before(new Date(new Date().getTime() + 1))); + } + + /** + * Check the email itself, and check it + * is as we would expect it to be + */ + { + MimeMessage msg = mailService.retrieveLastTestMessage(); + + assertEquals(1, msg.getAllRecipients().length); + assertEquals(inviteeEmail, msg.getAllRecipients()[0].toString()); + + assertEquals(1, msg.getFrom().length); + assertEquals(USER_MANAGER + "@alfrescotesting.com", msg.getFrom()[0].toString()); + + // Hasn't been sent, so no sent or received date + assertNull("Not been sent yet", msg.getSentDate()); + assertNull("Not been sent yet", msg.getReceivedDate()); + + // TODO - check some more details of the email + assertTrue((msg.getSubject().indexOf("You have been invited to join the") != -1)); + } + + /** + * Search for the new invitation + */ + List invitations = invitationService.listPendingInvitationsForResource(resourceType, resourceName); + assertTrue("invitations is empty", !invitations.isEmpty()); + + NominatedInvitation firstInvite = (NominatedInvitation) invitations.get(0); + assertEquals("invite id wrong", inviteId, firstInvite.getInviteId()); + assertEquals("first name wrong", inviteeFirstName, firstInvite.getInviteeFirstName()); + assertEquals("last name wrong", inviteeLastName, firstInvite.getInviteeLastName()); + assertEquals("user name wrong", inviteeUserName, firstInvite.getInviteeUserName()); + + /** + * Now accept the invitation + */ + NominatedInvitation acceptedInvitation = (NominatedInvitation) invitationService.accept(firstInvite + .getInviteId(), firstInvite.getTicket()); + assertEquals("invite id wrong", firstInvite.getInviteId(), acceptedInvitation.getInviteId()); + assertEquals("first name wrong", inviteeFirstName, acceptedInvitation.getInviteeFirstName()); + assertEquals("last name wrong", inviteeLastName, acceptedInvitation.getInviteeLastName()); + assertEquals("user name wrong", inviteeUserName, acceptedInvitation.getInviteeUserName()); + + List it4 = invitationService.listPendingInvitationsForResource(resourceType, resourceName); + assertTrue("invitations is not empty", it4.isEmpty()); + + /** + * Now get the invitation that we accepted + */ + NominatedInvitation acceptedInvitation2 = (NominatedInvitation) invitationService.getInvitation(firstInvite + .getInviteId()); + assertNotNull("get after accept does not return", acceptedInvitation2); + + /** + * Now verify access control list + */ + String roleName = siteService.getMembersRole(resourceName, inviteeUserName); + assertEquals("role name wrong", roleName, inviteeRole); + siteService.removeMembership(resourceName, inviteeUserName); + + + /** + * Check that system generated invitations can work as well + */ + { + Field faf = mailService.getClass().getDeclaredField("fromAddress"); + faf.setAccessible(true); + String defaultFromAddress = (String)ReflectionUtils.getField(faf, mailService); + + AuthenticationUtil.setFullyAuthenticatedUser(USER_NOEMAIL); + + // Check invitiation + NominatedInvitation nominatedInvitation2 = invitationService.inviteNominated(inviteeFirstName, inviteeLastName, + USER_TWO_EMAIL, resourceType, resourceName, inviteeRole, serverPath, acceptUrl, rejectUrl); + + assertNotNull("nominated invitation is null", nominatedInvitation2); + inviteId = nominatedInvitation.getInviteId(); + assertEquals("first name wrong", inviteeFirstName, nominatedInvitation2.getInviteeFirstName()); + assertEquals("last name wrong", inviteeLastName, nominatedInvitation2.getInviteeLastName()); + assertEquals("email name wrong", USER_TWO_EMAIL, nominatedInvitation2.getInviteeEmail()); + + // Check the email + MimeMessage msg = mailService.retrieveLastTestMessage(); + + assertEquals(1, msg.getAllRecipients().length); + assertEquals(USER_TWO_EMAIL, msg.getAllRecipients()[0].toString()); + + assertEquals(1, msg.getFrom().length); + assertEquals(defaultFromAddress, msg.getFrom()[0].toString()); + } + } + + // TODO MER START + /** + * Test nominated user - new user who rejects invitation + * + * @throws Exception + */ + public void testNominatedInvitationNewUserReject() throws Exception + { + Calendar calendar = Calendar.getInstance(); + calendar.add(Calendar.SECOND, -1); + Date startDate = calendar.getTime(); + + String inviteeFirstName = PERSON_FIRSTNAME; + String inviteeLastName = PERSON_LASTNAME; + String inviteeEmail = "123@alfrescotesting.com"; + String inviteeUserName = null; + Invitation.ResourceType resourceType = Invitation.ResourceType.WEB_SITE; + String resourceName = SITE_SHORT_NAME_INVITE; + String inviteeRole = SiteModel.SITE_COLLABORATOR; + String serverPath = "wibble"; + String acceptUrl = "froob"; + String rejectUrl = "marshmallow"; + + this.authenticationComponent.setCurrentUser(USER_MANAGER); + + NominatedInvitation nominatedInvitation = invitationService.inviteNominated(inviteeFirstName, inviteeLastName, + inviteeEmail, resourceType, resourceName, inviteeRole, serverPath, acceptUrl, rejectUrl); + + assertNotNull("nominated invitation is null", nominatedInvitation); + assertEquals("first name wrong", inviteeFirstName, nominatedInvitation.getInviteeFirstName()); + assertEquals("last name wrong", inviteeLastName, nominatedInvitation.getInviteeLastName()); + assertEquals("email name wrong", inviteeEmail, nominatedInvitation.getInviteeEmail()); + + // Generated User Name should be returned + inviteeUserName = nominatedInvitation.getInviteeUserName(); + assertNotNull("generated user name is null", inviteeUserName); + // sentInviteDate should be set to today + { + Date sentDate = nominatedInvitation.getSentInviteDate(); + assertTrue("sentDate wrong - too earlyStart Date: " +startDate +"\nSent Date: "+sentDate, sentDate.after(startDate)); + assertTrue("sentDate wrong - too lateStart Date: " +startDate +"\nSent Date: "+sentDate, sentDate.before(new Date(new Date().getTime() + 1))); + } + + /** + * Now reject the invitation + */ + NominatedInvitation rejectedInvitation = (NominatedInvitation) invitationService.reject(nominatedInvitation + .getInviteId(), "dont want it"); + assertEquals("invite id wrong", nominatedInvitation.getInviteId(), rejectedInvitation.getInviteId()); + assertEquals("first name wrong", inviteeFirstName, rejectedInvitation.getInviteeFirstName()); + assertEquals("last name wrong", inviteeLastName, rejectedInvitation.getInviteeLastName()); + assertEquals("user name wrong", inviteeUserName, rejectedInvitation.getInviteeUserName()); + + List it4 = invitationService.listPendingInvitationsForResource(resourceType, resourceName); + assertTrue("invitations is not empty", it4.isEmpty()); + + /** + * Now verify access control list inviteeUserName should not exist + */ + String roleName = siteService.getMembersRole(resourceName, inviteeUserName); + if (roleName != null) + { + fail("role has been set for a rejected user"); + } + + /** + * Now verify that the generated user has been removed + */ + if (personService.personExists(inviteeUserName)) + { + fail("generated user has not been cleaned up"); + } + } + + // TODO MER END + + /** + * Test nominated user - new user Creates two separate users with two the + * same email address. + * + * @throws Exception + */ + public void testNominatedInvitationNewUserSameEmails() throws Exception + { + String inviteeAFirstName = "John"; + String inviteeALastName = "Smith"; + + String inviteeBFirstName = "Jane"; + String inviteeBLastName = "Smith"; + + String inviteeEmail = "123@alfrescotesting.com"; + String inviteeAUserName = null; + String inviteeBUserName = null; + + Invitation.ResourceType resourceType = Invitation.ResourceType.WEB_SITE; + String resourceName = SITE_SHORT_NAME_INVITE; + String inviteeRole = SiteModel.SITE_COLLABORATOR; + String serverPath = "wibble"; + String acceptUrl = "froob"; + String rejectUrl = "marshmallow"; + + this.authenticationComponent.setCurrentUser(USER_MANAGER); + + NominatedInvitation nominatedInvitationA = invitationService.inviteNominated(inviteeAFirstName, + inviteeALastName, inviteeEmail, resourceType, resourceName, inviteeRole, serverPath, acceptUrl, + rejectUrl); + + assertNotNull("nominated invitation is null", nominatedInvitationA); + String inviteAId = nominatedInvitationA.getInviteId(); + assertEquals("first name wrong", inviteeAFirstName, nominatedInvitationA.getInviteeFirstName()); + assertEquals("last name wrong", inviteeALastName, nominatedInvitationA.getInviteeLastName()); + assertEquals("email name wrong", inviteeEmail, nominatedInvitationA.getInviteeEmail()); + + // Generated User Name should be returned + inviteeAUserName = nominatedInvitationA.getInviteeUserName(); + assertNotNull("generated user name is null", inviteeAUserName); + + NominatedInvitation nominatedInvitationB = invitationService.inviteNominated(inviteeBFirstName, + inviteeBLastName, inviteeEmail, resourceType, resourceName, inviteeRole, serverPath, acceptUrl, + rejectUrl); + + assertNotNull("nominated invitation is null", nominatedInvitationB); + String inviteBId = nominatedInvitationB.getInviteId(); + assertEquals("first name wrong", inviteeBFirstName, nominatedInvitationB.getInviteeFirstName()); + assertEquals("last name wrong", inviteeBLastName, nominatedInvitationB.getInviteeLastName()); + assertEquals("email name wrong", inviteeEmail, nominatedInvitationB.getInviteeEmail()); + + // Generated User Name should be returned + inviteeBUserName = nominatedInvitationB.getInviteeUserName(); + assertNotNull("generated user name is null", inviteeBUserName); + assertFalse("generated user names are the same", inviteeAUserName.equals(inviteeBUserName)); + + /** + * Now accept the invitation + */ + NominatedInvitation acceptedInvitationA = (NominatedInvitation) invitationService.accept(inviteAId, + nominatedInvitationA.getTicket()); + assertEquals("invite id wrong", inviteAId, acceptedInvitationA.getInviteId()); + assertEquals("first name wrong", inviteeAFirstName, acceptedInvitationA.getInviteeFirstName()); + assertEquals("last name wrong", inviteeALastName, acceptedInvitationA.getInviteeLastName()); + assertEquals("user name wrong", inviteeAUserName, acceptedInvitationA.getInviteeUserName()); + + NominatedInvitation acceptedInvitationB = (NominatedInvitation) invitationService.accept(inviteBId, + nominatedInvitationB.getTicket()); + assertEquals("invite id wrong", inviteBId, acceptedInvitationB.getInviteId()); + assertEquals("first name wrong", inviteeBFirstName, acceptedInvitationB.getInviteeFirstName()); + assertEquals("last name wrong", inviteeBLastName, acceptedInvitationB.getInviteeLastName()); + assertEquals("user name wrong", inviteeBUserName, acceptedInvitationB.getInviteeUserName()); + + /** + * Now verify access control list + */ + String roleNameA = siteService.getMembersRole(resourceName, inviteeAUserName); + assertEquals("role name wrong", roleNameA, inviteeRole); + String roleNameB = siteService.getMembersRole(resourceName, inviteeBUserName); + assertEquals("role name wrong", roleNameB, inviteeRole); + siteService.removeMembership(resourceName, inviteeAUserName); + siteService.removeMembership(resourceName, inviteeBUserName); + } + + /** + * Test nominated user - new user with whitespace in name. Related to + * ETHREEOH-3030. + */ + public void testNominatedInvitationNewUserWhitespace() throws Exception + { + String inviteeFirstName = PERSON_FIRSTNAME_SPACES; + String inviteeLastName = PERSON_LASTNAME_SPACES; + String resourceName = SITE_SHORT_NAME_INVITE; + String inviteeEmail = "123@alfrescotesting.com"; + Invitation.ResourceType resourceType = Invitation.ResourceType.WEB_SITE; + String inviteeRole = SiteModel.SITE_COLLABORATOR; + String serverPath = "wibble"; + String acceptUrl = "froob"; + String rejectUrl = "marshmallow"; + String expectedUserName = (inviteeFirstName + "_" + inviteeLastName).toLowerCase(); + expectedUserName = expectedUserName.replaceAll("\\s+", "_"); + authenticationComponent.setCurrentUser(USER_MANAGER); + + NominatedInvitation nominatedInvitation = invitationService.inviteNominated(inviteeFirstName, inviteeLastName, + inviteeEmail, resourceType, resourceName, inviteeRole, serverPath, acceptUrl, rejectUrl); + + assertNotNull("nominated invitation is null", nominatedInvitation); + assertEquals("Wrong username!", expectedUserName, nominatedInvitation.getInviteeUserName()); + + String inviteId = nominatedInvitation.getInviteId(); + + // Now we have an invitation get it and check the details have been + // returned correctly. + NominatedInvitation invitation = (NominatedInvitation) invitationService.getInvitation(inviteId); + assertNotNull("invitation is null", invitation); + assertEquals("first name wrong", inviteeFirstName, invitation.getInviteeFirstName()); + assertEquals("last name wrong", inviteeLastName, invitation.getInviteeLastName()); + assertEquals("user name wrong", expectedUserName, invitation.getInviteeUserName()); + + // Now accept the invitation + NominatedInvitation acceptedInvitation = (NominatedInvitation) invitationService.accept(invitation + .getInviteId(), invitation.getTicket()); + + assertEquals("first name wrong", inviteeFirstName, acceptedInvitation.getInviteeFirstName()); + assertEquals("last name wrong", inviteeLastName, acceptedInvitation.getInviteeLastName()); + assertEquals("user name wrong", expectedUserName, acceptedInvitation.getInviteeUserName()); + + // Now verify access control list + String roleName = siteService.getMembersRole(resourceName, expectedUserName); + assertEquals("role name wrong", roleName, inviteeRole); + siteService.removeMembership(resourceName, expectedUserName); + } + + /** + * Create a Nominated Invitation (for existing user, USER_ONE) read it. + * search for it cancel it search for it again (and fail to find it) Create + * a Nominated Invitation read it. search for it reject it Create a + * Nominated Invitation read it. accept it + */ + public void testNominatedInvitationExistingUser() throws Exception + { + String inviteeUserName = USER_ONE; + String inviteeEmail = USER_ONE_EMAIL; + String inviteeFirstName = USER_ONE_FIRSTNAME; + String inviteeLastName = USER_ONE_LASTNAME; + + Invitation.ResourceType resourceType = Invitation.ResourceType.WEB_SITE; + String resourceName = SITE_SHORT_NAME_INVITE; + String inviteeRole = SiteModel.SITE_COLLABORATOR; + String serverPath = "wibble"; + String acceptUrl = "froob"; + String rejectUrl = "marshmallow"; + + authenticationComponent.setCurrentUser(USER_MANAGER); + + NominatedInvitation nominatedInvitation = invitationService.inviteNominated(inviteeUserName, resourceType, + resourceName, inviteeRole, serverPath, acceptUrl, rejectUrl); + + assertNotNull("nominated invitation is null", nominatedInvitation); + String inviteId = nominatedInvitation.getInviteId(); + assertEquals("user name wrong", inviteeUserName, nominatedInvitation.getInviteeUserName()); + assertEquals("resource type name wrong", resourceType, nominatedInvitation.getResourceType()); + assertEquals("resource name wrong", resourceName, nominatedInvitation.getResourceName()); + assertEquals("role name wrong", inviteeRole, nominatedInvitation.getRoleName()); + assertEquals("server path wrong", serverPath, nominatedInvitation.getServerPath()); + assertEquals("accept URL wrong", acceptUrl, nominatedInvitation.getAcceptUrl()); + assertEquals("reject URL wrong", rejectUrl, nominatedInvitation.getRejectUrl()); + + // These values should be read from the person record + assertEquals("first name wrong", inviteeFirstName, nominatedInvitation.getInviteeFirstName()); + assertEquals("last name wrong", inviteeLastName, nominatedInvitation.getInviteeLastName()); + assertEquals("email name wrong", inviteeEmail, nominatedInvitation.getInviteeEmail()); + + /** + * Now we have an invitation get it and check the details have been + * returned correctly. + */ + NominatedInvitation invitation = (NominatedInvitation) invitationService.getInvitation(inviteId); + + assertNotNull("invitation is null", invitation); + assertEquals("invite id wrong", inviteId, invitation.getInviteId()); + assertEquals("user name wrong", inviteeUserName, nominatedInvitation.getInviteeUserName()); + assertEquals("resource type name wrong", resourceType, invitation.getResourceType()); + assertEquals("resource name wrong", resourceName, invitation.getResourceName()); + assertEquals("role name wrong", inviteeRole, invitation.getRoleName()); + assertEquals("server path wrong", serverPath, invitation.getServerPath()); + assertEquals("accept URL wrong", acceptUrl, invitation.getAcceptUrl()); + assertEquals("reject URL wrong", rejectUrl, invitation.getRejectUrl()); + + // These values should have been read from the DB + assertEquals("first name wrong", inviteeFirstName, invitation.getInviteeFirstName()); + assertEquals("last name wrong", inviteeLastName, invitation.getInviteeLastName()); + assertEquals("email name wrong", inviteeEmail, invitation.getInviteeEmail()); + + /** + * Search for the new invitation + */ + List invitations = invitationService.listPendingInvitationsForResource(resourceType, resourceName); + assertTrue("invitations is empty", !invitations.isEmpty()); + + NominatedInvitation firstInvite = (NominatedInvitation) invitations.get(0); + assertEquals("invite id wrong", inviteId, firstInvite.getInviteId()); + assertEquals("first name wrong", inviteeFirstName, firstInvite.getInviteeFirstName()); + assertEquals("last name wrong", inviteeLastName, firstInvite.getInviteeLastName()); + assertEquals("user name wrong", inviteeUserName, firstInvite.getInviteeUserName()); + + /** + * Now cancel the invitation + */ + NominatedInvitation canceledInvitation = (NominatedInvitation) invitationService.cancel(inviteId); + assertEquals("invite id wrong", inviteId, canceledInvitation.getInviteId()); + assertEquals("first name wrong", inviteeFirstName, canceledInvitation.getInviteeFirstName()); + assertEquals("last name wrong", inviteeLastName, canceledInvitation.getInviteeLastName()); + assertEquals("user name wrong", inviteeUserName, canceledInvitation.getInviteeUserName()); + + /** + * Do the query again - should no longer find anything + */ + List it2 = invitationService.listPendingInvitationsForResource(resourceType, resourceName); + assertTrue("invitations is not empty", it2.isEmpty()); + + /** + * Now invite and reject + */ + NominatedInvitation secondInvite = invitationService.inviteNominated(inviteeUserName, resourceType, + resourceName, inviteeRole, serverPath, acceptUrl, rejectUrl); + + NominatedInvitation rejectedInvitation = (NominatedInvitation) invitationService.cancel(secondInvite + .getInviteId()); + assertEquals("invite id wrong", secondInvite.getInviteId(), rejectedInvitation.getInviteId()); + assertEquals("user name wrong", inviteeUserName, rejectedInvitation.getInviteeUserName()); + + List it3 = invitationService.listPendingInvitationsForResource(resourceType, resourceName); + assertTrue("invitations is not empty", it3.isEmpty()); + + /** + * Now invite and accept + */ + NominatedInvitation thirdInvite = invitationService.inviteNominated(inviteeUserName, resourceType, + resourceName, inviteeRole, serverPath, acceptUrl, rejectUrl); + + NominatedInvitation acceptedInvitation = (NominatedInvitation) invitationService.accept(thirdInvite + .getInviteId(), thirdInvite.getTicket()); + assertEquals("invite id wrong", thirdInvite.getInviteId(), acceptedInvitation.getInviteId()); + assertEquals("first name wrong", inviteeFirstName, acceptedInvitation.getInviteeFirstName()); + assertEquals("last name wrong", inviteeLastName, acceptedInvitation.getInviteeLastName()); + assertEquals("user name wrong", inviteeUserName, acceptedInvitation.getInviteeUserName()); + + List it4 = invitationService.listPendingInvitationsForResource(resourceType, resourceName); + assertTrue("invitations is not empty", it4.isEmpty()); + + /** + * Now verify access control list + */ + String roleName = siteService.getMembersRole(resourceName, inviteeUserName); + assertEquals("role name wrong", inviteeRole, roleName); + siteService.removeMembership(resourceName, inviteeUserName); + } + + /** + * Create a moderated invitation Get it Search for it Cancel it Create a + * moderated invitation Reject the invitation Create a moderated invitation + * Approve the invitation + */ + public void testModeratedInvitation() + { + String inviteeUserName = USER_TWO; + Invitation.ResourceType resourceType = Invitation.ResourceType.WEB_SITE; + String resourceName = SITE_SHORT_NAME_INVITE; + String inviteeRole = SiteModel.SITE_COLLABORATOR; + String comments = "please sir, let me in!"; + + this.authenticationComponent.setCurrentUser(USER_TWO); + ModeratedInvitation invitation = invitationService.inviteModerated(comments, inviteeUserName, resourceType, + resourceName, inviteeRole); + + assertNotNull("moderated invitation is null", invitation); + String inviteId = invitation.getInviteId(); + assertEquals("user name wrong", inviteeUserName, invitation.getInviteeUserName()); + assertEquals("role name wrong", inviteeRole, invitation.getRoleName()); + assertEquals("comments", comments, invitation.getInviteeComments()); + assertEquals("resource type name wrong", resourceType, invitation.getResourceType()); + assertEquals("resource name wrong", resourceName, invitation.getResourceName()); + + /** + * Now we have an invitation get it and check the details have been + * returned correctly. + */ + ModeratedInvitation mi2 = (ModeratedInvitation) invitationService.getInvitation(inviteId); + assertEquals("invite id", inviteId, mi2.getInviteId()); + assertEquals("user name wrong", inviteeUserName, mi2.getInviteeUserName()); + assertEquals("role name wrong", inviteeRole, mi2.getRoleName()); + assertEquals("comments", comments, mi2.getInviteeComments()); + assertEquals("resource type name wrong", resourceType, mi2.getResourceType()); + assertEquals("resource name wrong", resourceName, mi2.getResourceName()); + + /** + * Search for the new invitation + */ + List invitations = invitationService.listPendingInvitationsForResource(resourceType, resourceName); + assertTrue("invitations is empty", !invitations.isEmpty()); + + ModeratedInvitation firstInvite = (ModeratedInvitation) invitations.get(0); + assertEquals("invite id wrong", inviteId, firstInvite.getInviteId()); + + /** + * Cancel the invitation + */ + ModeratedInvitation canceledInvitation = (ModeratedInvitation) invitationService.cancel(inviteId); + assertEquals("invite id wrong", inviteId, canceledInvitation.getInviteId()); + assertEquals("comments wrong", comments, canceledInvitation.getInviteeComments()); + + /** + * Should now be no invitation + */ + List inv2 = invitationService.listPendingInvitationsForResource(resourceType, resourceName); + assertTrue("After cancel invitations is not empty", inv2.isEmpty()); + + /** + * New invitation + */ + this.authenticationComponent.setCurrentUser(USER_TWO); + ModeratedInvitation invite2 = invitationService.inviteModerated(comments, inviteeUserName, resourceType, + resourceName, inviteeRole); + + String secondInvite = invite2.getInviteId(); + + this.authenticationComponent.setCurrentUser(USER_MANAGER); + invitationService.reject(secondInvite, "This is a test reject"); + + /** + * New invitation + */ + this.authenticationComponent.setCurrentUser(USER_TWO); + ModeratedInvitation invite3 = invitationService.inviteModerated(comments, inviteeUserName, resourceType, + resourceName, inviteeRole); + + String thirdInvite = invite3.getInviteId(); + + this.authenticationComponent.setCurrentUser(USER_MANAGER); + invitationService.approve(thirdInvite, "Welcome in"); + + /** + * Now verify access control list + */ + String roleName = siteService.getMembersRole(resourceName, inviteeUserName); + assertEquals("role name wrong", inviteeRole, roleName); + siteService.removeMembership(resourceName, inviteeUserName); + + } + + /** + * Test the approval of a moderated invitation + */ + public void testModeratedApprove() + { + String inviteeUserName = USER_TWO; + Invitation.ResourceType resourceType = Invitation.ResourceType.WEB_SITE; + String resourceName = SITE_SHORT_NAME_INVITE; + String inviteeRole = SiteModel.SITE_COLLABORATOR; + String comments = "please sir, let me in!"; + + /** + * New invitation from User TWO + */ + this.authenticationComponent.setCurrentUser(USER_TWO); + ModeratedInvitation invitation = invitationService.inviteModerated(comments, inviteeUserName, resourceType, + resourceName, inviteeRole); + + String invitationId = invitation.getInviteId(); + + /** + * Negative test Attempt to approve without the necessary role + */ + try + { + invitationService.approve(invitationId, "No Way Hosea!"); + assertTrue("excetion not thrown", false); + + } + catch (Exception e) + { + // An exception should have been thrown + e.printStackTrace(); + System.out.println(e.toString()); + } + + /** + * Approve the invitation + */ + this.authenticationComponent.setCurrentUser(USER_MANAGER); + invitationService.approve(invitationId, "Come on in"); + + /** + * Now verify access control list contains user two + */ + String roleName = siteService.getMembersRole(resourceName, inviteeUserName); + assertEquals("role name wrong", inviteeRole, roleName); + + /** + * Negative test attempt to approve an invitation that has aready been + * approved + */ + try + { + invitationService.approve(invitationId, "Have I not already done this?"); + assertTrue("duplicate approve excetion not thrown", false); + } + catch (Exception e) + { + // An exception should have been thrown + e.printStackTrace(); + System.out.println(e.toString()); + } + /** + * Negative test User is already a member of the site + */ + siteService.removeMembership(resourceName, inviteeUserName); + } + + /** + * Tests of Moderated Reject + */ + public void testModeratedReject() + { + String inviteeUserName = USER_TWO; + Invitation.ResourceType resourceType = Invitation.ResourceType.WEB_SITE; + String resourceName = SITE_SHORT_NAME_INVITE; + String inviteeRole = SiteModel.SITE_COLLABORATOR; + String comments = "please sir, let me in!"; + + /** + * New invitation from User TWO + */ + this.authenticationComponent.setCurrentUser(USER_TWO); + ModeratedInvitation invitation = invitationService.inviteModerated(comments, inviteeUserName, resourceType, + resourceName, inviteeRole); + + String invitationId = invitation.getInviteId(); + + /** + * Negative test Attempt to reject without the necessary role + */ + try + { + invitationService.reject(invitationId, "No Way Hosea!"); + assertTrue("excetion not thrown", false); + + } + catch (Exception e) + { + // An exception should have been thrown + e.printStackTrace(); + System.out.println(e.toString()); + } + + /** + * Reject the invitation + */ + this.authenticationComponent.setCurrentUser(USER_MANAGER); + invitationService.reject(invitationId, "Go away!"); + + /** + * Negative test attempt to approve an invitation that has been rejected + */ + try + { + invitationService.approve(invitationId, "Have I not rejected this?"); + assertTrue("rejected invitation not working", false); + } + catch (Exception e) + { + // An exception should have been thrown + e.printStackTrace(); + System.out.println(e.toString()); + } + } + + /** + * Test search invitation + */ + public void testSearchInvitation() + { + /** + * Make up a tree of invitations and then search Resource, User, + * Workflow 1) RED, One, Moderated 2) RED, One, Nominated 3) BLUE, One, + * Nominated 4) RED, Two, Moderated + */ + Invitation.ResourceType resourceType = Invitation.ResourceType.WEB_SITE; + String inviteeRole = SiteModel.SITE_COLLABORATOR; + String comments = "please sir, let me in!"; + String serverPath = "wibble"; + String acceptUrl = "froob"; + String rejectUrl = "marshmallow"; + + this.authenticationComponent.setCurrentUser(USER_MANAGER); + invitationService.inviteModerated(comments, USER_ONE, resourceType, SITE_SHORT_NAME_RED, inviteeRole); + + invitationService.inviteNominated(USER_ONE, resourceType, SITE_SHORT_NAME_RED, inviteeRole, serverPath, + acceptUrl, rejectUrl); + + NominatedInvitation invitationThree = invitationService.inviteNominated(USER_ONE, resourceType, + SITE_SHORT_NAME_BLUE, inviteeRole, serverPath, acceptUrl, rejectUrl); + String threeId = invitationThree.getInviteId(); + + invitationService.inviteModerated(comments, USER_TWO, resourceType, SITE_SHORT_NAME_RED, inviteeRole); + + /** + * Search for invitations for BLUE + */ + List resOne = invitationService.listPendingInvitationsForResource(ResourceType.WEB_SITE, + SITE_SHORT_NAME_BLUE); + assertEquals("blue invites not 1", 1, resOne.size()); + assertEquals("blue id wrong", threeId, resOne.get(0).getInviteId()); + + /** + * Search for invitations for RED + */ + List resTwo = invitationService.listPendingInvitationsForResource(ResourceType.WEB_SITE, + SITE_SHORT_NAME_RED); + assertEquals("red invites not 3", 3, resTwo.size()); + + /** + * Search for invitations for USER_ONE + */ + List resThree = invitationService.listPendingInvitationsForInvitee(USER_ONE); + assertEquals("user one does not have 3 invitations", 3, resThree.size()); + + /** + * Search for invitations for USER_TWO + */ + List resFour = invitationService.listPendingInvitationsForInvitee(USER_TWO); + assertEquals("user two does not have 1 invitations", 1, resFour.size()); + + /** + * Search for user1's nominated invitations + */ + InvitationSearchCriteriaImpl crit1 = new InvitationSearchCriteriaImpl(); + crit1.setInvitee(USER_ONE); + crit1.setInvitationType(InvitationSearchCriteria.InvitationType.NOMINATED); + + List resFive = invitationService.searchInvitation(crit1); + assertEquals("user one does not have 2 nominated", 2, resFive.size()); + + /** + * Search with an empty criteria - should find all open invitations + */ + InvitationSearchCriteria crit2 = new InvitationSearchCriteriaImpl(); + invitationService.searchInvitation(crit2); + assertTrue("search everything returned 0 elements", resFive.size() > 0); + + InvitationSearchCriteriaImpl crit3 = new InvitationSearchCriteriaImpl(); + crit3.setInviter(USER_MANAGER); + crit3.setInvitationType(InvitationSearchCriteria.InvitationType.NOMINATED); + + List res3 = invitationService.searchInvitation(crit3); + assertEquals("user one does not have 2 nominated", 2, res3.size()); + + } + + public void disabled_test100Invites() throws Exception + { + Invitation.ResourceType resourceType = Invitation.ResourceType.WEB_SITE; + String resourceName = SITE_SHORT_NAME_INVITE; + String inviteeRole = SiteModel.SITE_COLLABORATOR; + String serverPath = "wibble"; + String acceptUrl = "froob"; + String rejectUrl = "marshmallow"; + + authenticationComponent.setCurrentUser(USER_MANAGER); + + // Create 1000 invites + for (int i = 0; i < 1000; i++) + { + invitationService.inviteNominated(USER_ONE, resourceType, resourceName, inviteeRole, serverPath, acceptUrl, rejectUrl); + } + + // Invite USER_TWO + NominatedInvitation invite = invitationService.inviteNominated(USER_TWO, resourceType, resourceName, inviteeRole, serverPath, acceptUrl, rejectUrl); + + InvitationSearchCriteriaImpl query = new InvitationSearchCriteriaImpl(); + query.setInvitee(USER_TWO); + + long start = System.currentTimeMillis(); + List results = invitationService.searchInvitation(query); + long end= System.currentTimeMillis(); + System.out.println("Invitation Search took " + (end - start) + "ms."); + + assertEquals(1, results.size()); + assertEquals(invite.getInviteId(), results.get(0).getInviteId()); + this.setComplete(); + this.endTransaction(); + + } + + public void testGetInvitation() + { + try + { + /** + * Get an invitation that does not exist. + */ + invitationService.getInvitation("jbpm$99999999"); + fail("should have thrown an exception"); + } + catch (Exception e) + { + // should have gone here + } + } + + private void createPerson(String userName, String emailAddress, String firstName, String lastName) + { + // if user with given user name doesn't already exist then create user + if (this.authenticationService.authenticationExists(userName) == false) + { + // create user + this.authenticationService.createAuthentication(userName, "password".toCharArray()); + } + + // if person node with given user name doesn't already exist then create + // person + if (this.personService.personExists(userName) == false) + { + // create person properties + PropertyMap personProps = new PropertyMap(); + personProps.put(ContentModel.PROP_USERNAME, userName); + personProps.put(ContentModel.PROP_FIRSTNAME, firstName); + personProps.put(ContentModel.PROP_LASTNAME, lastName); + personProps.put(ContentModel.PROP_EMAIL, emailAddress); + personProps.put(ContentModel.PROP_JOBTITLE, PERSON_JOBTITLE); + personProps.put(ContentModel.PROP_ORGANIZATION, PERSON_ORG); + + // create person node for user + this.personService.createPerson(personProps); + } + } + + private void deletePersonByUserName(String userName) + { + // delete person node associated with given user name + // if one exists + if (this.personService.personExists(userName)) + { + this.personService.deletePerson(userName); + } + } +} diff --git a/source/java/org/alfresco/repo/invitation/InvitationServiceImpl.java b/source/java/org/alfresco/repo/invitation/InvitationServiceImpl.java index 77269b53a2..2c346cef8d 100644 --- a/source/java/org/alfresco/repo/invitation/InvitationServiceImpl.java +++ b/source/java/org/alfresco/repo/invitation/InvitationServiceImpl.java @@ -22,6 +22,7 @@ package org.alfresco.repo.invitation; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -34,13 +35,16 @@ import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.security.authentication.PasswordGenerator; import org.alfresco.repo.security.authentication.UserNameGenerator; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.site.SiteModel; +import org.alfresco.repo.workflow.CancelWorkflowActionExecuter; import org.alfresco.repo.workflow.WorkflowModel; import org.alfresco.repo.workflow.activiti.ActivitiConstants; import org.alfresco.repo.workflow.jbpm.JBPMEngine; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ActionService; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.invitation.Invitation; import org.alfresco.service.cmr.invitation.InvitationException; @@ -94,6 +98,7 @@ public class InvitationServiceImpl implements InvitationService, NodeServicePoli */ private WorkflowService workflowService; private WorkflowAdminService workflowAdminService; + private ActionService actionService; private PersonService personService; private SiteService siteService; private MutableAuthenticationService authenticationService; @@ -132,6 +137,7 @@ public class InvitationServiceImpl implements InvitationService, NodeServicePoli { PropertyCheck.mandatory(this, "nodeService", nodeService); PropertyCheck.mandatory(this, "WorkflowService", workflowService); + PropertyCheck.mandatory(this, "ActionService", actionService); PropertyCheck.mandatory(this, "PersonService", personService); PropertyCheck.mandatory(this, "SiteService", siteService); PropertyCheck.mandatory(this, "AuthenticationService", authenticationService); @@ -515,6 +521,34 @@ public class InvitationServiceImpl implements InvitationService, NodeServicePoli } return invitation; } + + private Map getInvitationTasks(List invitationIds) + { + for (String invitationId: invitationIds) + { + validateInvitationId(invitationId); + } + + // query for invite workflow task associate + long start = (logger.isDebugEnabled()) ? System.currentTimeMillis() : 0; + List inviteStartTasks = workflowService.getStartTasks(invitationIds, true); + if (logger.isDebugEnabled()) + { + logger.debug(" getInvitationTask("+invitationIds.size()+") in "+ (System.currentTimeMillis()-start) + " ms"); + } + + Map result = new HashMap(inviteStartTasks.size() * 2); + for(WorkflowTask inviteStartTask: inviteStartTasks) + { + String invitationId = inviteStartTask.getPath().getInstance().getId(); + // The following does not work for moderated tasks + // String invitationId = (String) + // inviteStartTask.getProperties().get(WorkflowModel.PROP_WORKFLOW_INSTANCE_ID); + result.put(invitationId, inviteStartTask); + } + + return result; + } private ModeratedInvitation getModeratedInvitation(WorkflowTask startTask) { @@ -586,44 +620,97 @@ public class InvitationServiceImpl implements InvitationService, NodeServicePoli */ public List listPendingInvitationsForResource(Invitation.ResourceType resourceType, String resourceName) { - InvitationSearchCriteriaImpl crit = new InvitationSearchCriteriaImpl(); - crit.setInvitationType(InvitationSearchCriteria.InvitationType.ALL); - crit.setResourceType(resourceType); - crit.setResourceName(resourceName); - return searchInvitation(crit); + InvitationSearchCriteriaImpl criteria = getPendingInvitationCriteriaForResource(resourceType, resourceName); + return searchInvitation(criteria); } /** - * This is the general search invitation method + * Returns search criteria to find pending invitations + * @param resourceType + * @param resourceName + * @return search criteria + */ + private InvitationSearchCriteriaImpl getPendingInvitationCriteriaForResource( + Invitation.ResourceType resourceType, String resourceName) + { + InvitationSearchCriteriaImpl criteria = new InvitationSearchCriteriaImpl(); + criteria.setInvitationType(InvitationSearchCriteria.InvitationType.ALL); + criteria.setResourceType(resourceType); + criteria.setResourceName(resourceName); + return criteria; + } + + /** + * This is the general search invitation method returning {@link Invitation}s * * @param criteria - * @return the list of invitations + * @return the list of start tasks for invitations */ public List searchInvitation(final InvitationSearchCriteria criteria) { - List searchResults = new ArrayList(); + int limit = 200; + List invitationIds = searchInvitationsForIds(criteria, limit); + return invitationIds.isEmpty() ? Collections.emptyList() : searchInvitation(criteria, invitationIds); + } + + private List searchInvitation(final InvitationSearchCriteria criteria, List invitationIds) + { + final Map taskCache = getInvitationTasks(invitationIds); + return CollectionUtils.transform(invitationIds, new Function() + { + public Invitation apply(String invitationId) + { + WorkflowTask startTask = taskCache.get(invitationId); + if (startTask == null) + { + return null; + } + Invitation invitation = getInvitation(startTask); + return invitationMatches(invitation, criteria) ? invitation : null; + } + }); + } + + /** + * This is a general search invitation method returning IDs + * + * @param criteria + * @param limit maximum number of IDs to return. If less than 1, there is no limit. + * @return the list of invitation IDs (the IDs of the invitations not the IDs of the invitation start tasks) + */ + private List searchInvitationsForIds(final InvitationSearchCriteria criteria, int limit) + { + List invitationIds = new ArrayList(); InvitationSearchCriteria.InvitationType toSearch = criteria.getInvitationType(); if (toSearch == InvitationSearchCriteria.InvitationType.ALL || toSearch == InvitationSearchCriteria.InvitationType.NOMINATED) { - searchResults.addAll(searchNominatedInvitations(criteria)); - } - if (toSearch == InvitationSearchCriteria.InvitationType.ALL - || toSearch == InvitationSearchCriteria.InvitationType.MODERATED) - { - searchResults.addAll(searchModeratedInvitations(criteria)); - } - - return CollectionUtils.transform(searchResults, new Function() - { - public Invitation apply(WorkflowTask task) + for (WorkflowTask task : searchNominatedInvitations(criteria)) { String invitationId = task.getPath().getInstance().getId(); - Invitation invitation = getInvitation(invitationId); - return invitationMatches(invitation, criteria) ? invitation : null; + invitationIds.add(invitationId); + if (limit > 0 && invitationIds.size() >= limit) + { + break; + } } + } + if ((limit <= 0 || invitationIds.size() < limit) && + (toSearch == InvitationSearchCriteria.InvitationType.ALL + || toSearch == InvitationSearchCriteria.InvitationType.MODERATED)) + { + for (WorkflowTask task: searchModeratedInvitations(criteria)) + { + String invitationId = task.getPath().getInstance().getId(); + invitationIds.add(invitationId); + if (limit > 0 && invitationIds.size() >= limit) + { + break; + } + } + } + return invitationIds; - }); } /** @@ -665,6 +752,8 @@ public class InvitationServiceImpl implements InvitationService, NodeServicePoli private List searchModeratedInvitations(InvitationSearchCriteria criteria) { + long start = (logger.isDebugEnabled()) ? System.currentTimeMillis() : 0; + WorkflowTaskQuery query = new WorkflowTaskQuery(); query.setTaskState(WorkflowTaskState.IN_PROGRESS); @@ -694,7 +783,8 @@ public class InvitationServiceImpl implements InvitationService, NodeServicePoli if(workflowAdminService.isEngineEnabled(JBPMEngine.ENGINE_ID)) { query.setTaskName(WorkflowModelModeratedInvitation.WF_REVIEW_TASK); - List jbpmTasks = this.workflowService.queryTasks(query); + List jbpmTasks = this.workflowService.queryTasks(query, true); + if(jbpmTasks !=null) { results.addAll(jbpmTasks); @@ -703,17 +793,23 @@ public class InvitationServiceImpl implements InvitationService, NodeServicePoli if(workflowAdminService.isEngineEnabled(ActivitiConstants.ENGINE_ID)) { query.setTaskName(WorkflowModelModeratedInvitation.WF_ACTIVITI_REVIEW_TASK); - List jbpmTasks = this.workflowService.queryTasks(query); + List jbpmTasks = this.workflowService.queryTasks(query, true); if(jbpmTasks !=null) { results.addAll(jbpmTasks); } } + if (logger.isDebugEnabled()) + { + logger.debug(" searchModeratedInvitations in "+ (System.currentTimeMillis()-start) + " ms"); + } return results; } private List searchNominatedInvitations(InvitationSearchCriteria criteria) { + long start = (logger.isDebugEnabled()) ? System.currentTimeMillis() : 0; + WorkflowTaskQuery query = new WorkflowTaskQuery(); query.setTaskState(WorkflowTaskState.IN_PROGRESS); @@ -749,7 +845,7 @@ public class InvitationServiceImpl implements InvitationService, NodeServicePoli if(workflowAdminService.isEngineEnabled(JBPMEngine.ENGINE_ID)) { query.setTaskName(WorkflowModelNominatedInvitation.WF_TASK_INVITE_PENDING); - List jbpmTasks = this.workflowService.queryTasks(query); + List jbpmTasks = this.workflowService.queryTasks(query, true); if(jbpmTasks !=null) { results.addAll(jbpmTasks); @@ -758,12 +854,16 @@ public class InvitationServiceImpl implements InvitationService, NodeServicePoli if(workflowAdminService.isEngineEnabled(ActivitiConstants.ENGINE_ID)) { query.setTaskName(WorkflowModelNominatedInvitation.WF_TASK_ACTIVIT_INVITE_PENDING); - List jbpmTasks = this.workflowService.queryTasks(query); + List jbpmTasks = this.workflowService.queryTasks(query, true); if(jbpmTasks !=null) { results.addAll(jbpmTasks); } } + if (logger.isDebugEnabled()) + { + logger.debug(" searchNominatedInvitations in "+ (System.currentTimeMillis()-start) + " ms"); + } return results; } @@ -794,6 +894,14 @@ public class InvitationServiceImpl implements InvitationService, NodeServicePoli { return workflowService; } + + /** + * @param actionService the actionService to set + */ + public void setActionService(ActionService actionService) + { + this.actionService = actionService; + } public void setPersonService(PersonService personService) { @@ -1329,13 +1437,34 @@ public class InvitationServiceImpl implements InvitationService, NodeServicePoli String siteName = (String) nodeService.getProperty(siteRef, ContentModel.PROP_NAME); if (siteName != null) { - logger.debug("Invitation service delete node fired " + type + ", " + siteName); - List invitations = listPendingInvitationsForResource( - Invitation.ResourceType.WEB_SITE, siteName); - for (Invitation invitation : invitations) + long start =0; + if (logger.isDebugEnabled()) { - logger.debug("cancel workflow " + invitation.getInviteId()); - workflowService.cancelWorkflow(invitation.getInviteId()); + logger.debug("Invitation service delete node fired " + type + ", " + siteName); + start = System.currentTimeMillis(); + } + InvitationSearchCriteriaImpl criteria = + getPendingInvitationCriteriaForResource(Invitation.ResourceType.WEB_SITE, siteName); + List invitationIds = searchInvitationsForIds(criteria, -1); + + if (logger.isDebugEnabled()) + { + long end = System.currentTimeMillis(); + logger.debug("Invitations found: " + invitationIds.size() + " in "+ ((end-start)/1000) + " seconds"); + start = System.currentTimeMillis(); + } + + // Create the action + Action action = actionService.createAction(CancelWorkflowActionExecuter.NAME); + action.setParameterValue(CancelWorkflowActionExecuter.PARAM_WORKFLOW_ID_LIST, (Serializable)invitationIds); + + // Cancel the workflows asynchronously + actionService.executeAction(action, siteRef, false, true); + + if (logger.isDebugEnabled()) + { + long end = System.currentTimeMillis(); + logger.debug("Invitations cancelled: " + invitationIds.size() + " in "+ (end-start) + " ms"); } } } diff --git a/source/java/org/alfresco/repo/jscript/app/VtiServerCustomResponse.java b/source/java/org/alfresco/repo/jscript/app/VtiServerCustomResponse.java index c29124116e..e4a63d6cf0 100644 --- a/source/java/org/alfresco/repo/jscript/app/VtiServerCustomResponse.java +++ b/source/java/org/alfresco/repo/jscript/app/VtiServerCustomResponse.java @@ -22,9 +22,7 @@ import org.alfresco.repo.admin.SysAdminParams; import org.alfresco.repo.jscript.ScriptUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.apache.ibatis.migration.commands.StatusCommand; import org.springframework.beans.BeansException; -import org.springframework.context.ApplicationContext; import java.io.Serializable; import java.util.LinkedHashMap; @@ -43,6 +41,7 @@ public class VtiServerCustomResponse implements CustomResponse private int vtiServerPort = 0; private String vtiServerHost; + private String vtiServerProtocol; private SysAdminParams sysAdminParams; private ScriptUtils scriptUtils; @@ -86,6 +85,16 @@ public class VtiServerCustomResponse implements CustomResponse this.vtiServerHost = vtiServerHost; } + /** + * Setter for vtiServer Protocol + * + * @param vtiServerProtocol + */ + public void setProtocol(String vtiServerProtocol) + { + this.vtiServerProtocol = vtiServerProtocol; + } + /** * Populates the DocLib webscript response with custom metadata * @@ -110,6 +119,10 @@ public class VtiServerCustomResponse implements CustomResponse { jsonObj.put("host", this.sysAdminParams.subsituteHost(this.vtiServerHost)); } + if (this.vtiServerProtocol != null) + { + jsonObj.put("protocol", this.vtiServerProtocol); + } return (Serializable)jsonObj; } catch (Exception e) diff --git a/source/java/org/alfresco/repo/management/SafeApplicationEventMulticaster.java b/source/java/org/alfresco/repo/management/SafeApplicationEventMulticaster.java index 8a7a0f6275..4d0df432fe 100644 --- a/source/java/org/alfresco/repo/management/SafeApplicationEventMulticaster.java +++ b/source/java/org/alfresco/repo/management/SafeApplicationEventMulticaster.java @@ -325,6 +325,8 @@ public class SafeApplicationEventMulticaster implements ApplicationEventMulticas @Override public boolean equals(Object other) { + if (other == null) + return false; if (this == other) { return true; diff --git a/source/java/org/alfresco/repo/model/ml/tools/MultilingualContentServiceImplTest.java b/source/java/org/alfresco/repo/model/ml/tools/MultilingualContentServiceImplTest.java index 228edc8ea4..7a8e3d2d5f 100644 --- a/source/java/org/alfresco/repo/model/ml/tools/MultilingualContentServiceImplTest.java +++ b/source/java/org/alfresco/repo/model/ml/tools/MultilingualContentServiceImplTest.java @@ -114,7 +114,7 @@ public class MultilingualContentServiceImplTest extends AbstractMultilingualTest "Real size : " + missing.size() + ". Normal Size " + (langListSize - 3), missing.size() != (langListSize - 3)); // make sure that the missing language list is correct - assertFalse("Missing Translation List false. Locale " + loc2 + " or " + loc3 + " found", missing.contains(loc2.toString()) || missing.contains(loc3.toString())); + assertFalse("Missing Translation List false. Locale " + loc2.toString() + " or " + loc3.toString() + " found", missing.contains(loc2) || missing.contains(loc3)); } public void testGetTranslationForLocale() throws Exception diff --git a/source/java/org/alfresco/repo/module/tool/ModuleDetailsHelper.java b/source/java/org/alfresco/repo/module/tool/ModuleDetailsHelper.java index 3a8f5f6b14..2250731ef4 100644 --- a/source/java/org/alfresco/repo/module/tool/ModuleDetailsHelper.java +++ b/source/java/org/alfresco/repo/module/tool/ModuleDetailsHelper.java @@ -170,4 +170,32 @@ public class ModuleDetailsHelper exception); } } + + + /** + * Reads a .properites file from the war and returns it as a Properties object + * @param warLocation The location of the war + * @param propertiesPath Path to the properties file (including .properties) + * @return Properties object or null + */ + public static Properties getPropertiesFromWar(String warLocation, String propertiesPath) + { + Properties result = null; + try + { + File file = new File(warLocation+propertiesPath, ModuleManagementTool.DETECTOR_AMP_AND_WAR); + if (file.exists()) + { + InputStream is = new FileInputStream(file); + result = new Properties(); + result.load(is); + } + } + catch (IOException exception) + { + throw new ModuleManagementToolException("Unable to load properties from the war file; "+propertiesPath, exception); + } + return result; + + } } diff --git a/source/java/org/alfresco/repo/module/tool/ModuleManagementTool.java b/source/java/org/alfresco/repo/module/tool/ModuleManagementTool.java index 3936de9cb5..34dacca2f2 100644 --- a/source/java/org/alfresco/repo/module/tool/ModuleManagementTool.java +++ b/source/java/org/alfresco/repo/module/tool/ModuleManagementTool.java @@ -66,6 +66,7 @@ public class ModuleManagementTool /** Standard directories found in the alfresco war */ public static final String BACKUP_DIR = WarHelper.MODULE_NAMESPACE_DIR+ "/backup"; + public static final String VERSION_PROPERTIES = "/WEB-INF/classes/alfresco/version.properties"; /** Operations and options supperted via the command line interface to this class */ private static final String OP_INSTALL = "install"; @@ -231,6 +232,9 @@ public class ModuleManagementTool } String installingId = installingModuleDetails.getId(); VersionNumber installingVersion = installingModuleDetails.getVersion(); + + //Check that the target war repo is the correct version + checkTargetWarVersion(warFileLocation, installingModuleDetails); //A series of checks warHelper.checkCompatibleVersion(theWar, installingModuleDetails); @@ -372,6 +376,20 @@ public class ModuleManagementTool } } + protected void checkTargetWarVersion(String warFileLocation, ModuleDetails installingModuleDetails) + { + Properties warVers = ModuleDetailsHelper.getPropertiesFromWar(warFileLocation, VERSION_PROPERTIES); + outputMessage("WAR properties '" + warVers + "'"); + VersionNumber warVersion = new VersionNumber(warVers.getProperty("version.major")+"."+warVers.getProperty("version.revision")+"."+warVers.getProperty("version.minor")); + if(warVersion.compareTo(installingModuleDetails.getRepoVersionMin())==-1) { + throw new ModuleManagementToolException("The module ("+installingModuleDetails.getTitle()+") must be installed on a repo version greater than "+installingModuleDetails.getRepoVersionMin()); + } + if(warVersion.compareTo(installingModuleDetails.getRepoVersionMax())==1) { + throw new ModuleManagementToolException("The module ("+installingModuleDetails.getTitle()+") cannot be installed on a repo version greater than "+installingModuleDetails.getRepoVersionMax()); + } + + } + private void backupWar(String warFileLocation, boolean backupWAR) { diff --git a/source/java/org/alfresco/repo/module/tool/ModuleManagementToolTest.java b/source/java/org/alfresco/repo/module/tool/ModuleManagementToolTest.java index 9e8127a362..49d301191c 100644 --- a/source/java/org/alfresco/repo/module/tool/ModuleManagementToolTest.java +++ b/source/java/org/alfresco/repo/module/tool/ModuleManagementToolTest.java @@ -30,7 +30,10 @@ import java.util.Map; import junit.framework.TestCase; +import org.alfresco.repo.module.ModuleDetailsImpl; +import org.alfresco.service.cmr.module.ModuleDetails; import org.alfresco.util.TempFileProvider; +import org.alfresco.util.VersionNumber; import org.springframework.util.FileCopyUtils; import de.schlichtherle.io.DefaultRaesZipDetector; @@ -264,10 +267,48 @@ public class ModuleManagementToolTest extends TestCase } catch (ModuleManagementToolException exception) { - exception.printStackTrace(); System.out.println("Expected failure: " + exception.getMessage()); } } + + public void testCheckTargetWarVersion() throws Exception + { + manager.setVerbose(true); + String warLocation = getFileLocation(".war", "module/test.war"); //Version 4.0.1 + + ModuleDetails installingModuleDetails = new ModuleDetailsImpl("test_it", new VersionNumber("9999"), "Test Mod", "Testing module"); + installingModuleDetails.setRepoVersionMin(new VersionNumber("10.1")); + try + { + this.manager.checkTargetWarVersion(warLocation, installingModuleDetails); + fail(); //should never get here + } + catch (ModuleManagementToolException exception) + { + assertTrue(exception.getMessage().endsWith("must be installed on a repo version greater than 10.1")); + } + + installingModuleDetails.setRepoVersionMin(new VersionNumber("1.1")); + this.manager.checkTargetWarVersion(warLocation, installingModuleDetails); //does not throw exception + + installingModuleDetails.setRepoVersionMax(new VersionNumber("3.0")); + try + { + this.manager.checkTargetWarVersion(warLocation, installingModuleDetails); + fail(); //should never get here + } + catch (ModuleManagementToolException exception) + { + assertTrue(exception.getMessage().endsWith("cannot be installed on a repo version greater than 3.0")); + } + + installingModuleDetails.setRepoVersionMax(new VersionNumber("99")); + this.manager.checkTargetWarVersion(warLocation, installingModuleDetails); //does not throw exception + + installingModuleDetails.setRepoVersionMin(new VersionNumber("4.0.1")); //current war version + installingModuleDetails.setRepoVersionMax(new VersionNumber("4.0.1")); //current war version + this.manager.checkTargetWarVersion(warLocation, installingModuleDetails); //does not throw exception + } public void testList() diff --git a/source/java/org/alfresco/repo/node/AbstractNodeServiceImpl.java b/source/java/org/alfresco/repo/node/AbstractNodeServiceImpl.java index d742db2e14..6819d48fa1 100644 --- a/source/java/org/alfresco/repo/node/AbstractNodeServiceImpl.java +++ b/source/java/org/alfresco/repo/node/AbstractNodeServiceImpl.java @@ -33,6 +33,7 @@ import org.alfresco.repo.node.NodeServicePolicies.BeforeCreateStorePolicy; import org.alfresco.repo.node.NodeServicePolicies.BeforeDeleteChildAssociationPolicy; import org.alfresco.repo.node.NodeServicePolicies.BeforeDeleteNodePolicy; import org.alfresco.repo.node.NodeServicePolicies.BeforeRemoveAspectPolicy; +import org.alfresco.repo.node.NodeServicePolicies.BeforeSetNodeTypePolicy; import org.alfresco.repo.node.NodeServicePolicies.BeforeUpdateNodePolicy; import org.alfresco.repo.node.NodeServicePolicies.OnAddAspectPolicy; import org.alfresco.repo.node.NodeServicePolicies.OnCreateAssociationPolicy; @@ -46,6 +47,7 @@ import org.alfresco.repo.node.NodeServicePolicies.OnDeleteNodePolicy; import org.alfresco.repo.node.NodeServicePolicies.OnMoveNodePolicy; import org.alfresco.repo.node.NodeServicePolicies.OnRemoveAspectPolicy; import org.alfresco.repo.node.NodeServicePolicies.OnRestoreNodePolicy; +import org.alfresco.repo.node.NodeServicePolicies.OnSetNodeTypePolicy; import org.alfresco.repo.node.NodeServicePolicies.OnUpdateNodePolicy; import org.alfresco.repo.node.NodeServicePolicies.OnUpdatePropertiesPolicy; import org.alfresco.repo.policy.AssociationPolicyDelegate; @@ -109,6 +111,8 @@ public abstract class AbstractNodeServiceImpl implements NodeService private ClassPolicyDelegate onMoveNodeDelegate; private ClassPolicyDelegate beforeUpdateNodeDelegate; private ClassPolicyDelegate onUpdateNodeDelegate; + private ClassPolicyDelegate onSetNodeTypeDelegate; + private ClassPolicyDelegate beforeSetNodeTypeDelegate; private ClassPolicyDelegate onUpdatePropertiesDelegate; private ClassPolicyDelegate beforeDeleteNodeDelegate; private ClassPolicyDelegate onDeleteNodeDelegate; @@ -197,6 +201,8 @@ public abstract class AbstractNodeServiceImpl implements NodeService onMoveNodeDelegate = policyComponent.registerClassPolicy(NodeServicePolicies.OnMoveNodePolicy.class); beforeUpdateNodeDelegate = policyComponent.registerClassPolicy(NodeServicePolicies.BeforeUpdateNodePolicy.class); onUpdateNodeDelegate = policyComponent.registerClassPolicy(NodeServicePolicies.OnUpdateNodePolicy.class); + onSetNodeTypeDelegate = policyComponent.registerClassPolicy(NodeServicePolicies.OnSetNodeTypePolicy.class); + beforeSetNodeTypeDelegate = policyComponent.registerClassPolicy(NodeServicePolicies.BeforeSetNodeTypePolicy.class); onUpdatePropertiesDelegate = policyComponent.registerClassPolicy(NodeServicePolicies.OnUpdatePropertiesPolicy.class); beforeDeleteNodeDelegate = policyComponent.registerClassPolicy(NodeServicePolicies.BeforeDeleteNodePolicy.class); onDeleteNodeDelegate = policyComponent.registerClassPolicy(NodeServicePolicies.OnDeleteNodePolicy.class); @@ -347,6 +353,40 @@ public abstract class AbstractNodeServiceImpl implements NodeService policy.onUpdateNode(nodeRef); } + /** + * @see NodeServicePolicies.OnSetNodeTypePolicy#onSetNodeType(NodeRef, QName, QName) + */ + protected void invokeOnSetType(NodeRef nodeRef, QName oldType, QName newType) + { + if (ignorePolicy(nodeRef)) + { + return; + } + + // get qnames to invoke against + Set qnames = getTypeAndAspectQNames(nodeRef); + // execute policy for node type and aspects + NodeServicePolicies.OnSetNodeTypePolicy policy = onSetNodeTypeDelegate.get(nodeRef, qnames); + policy.onSetNodeType(nodeRef, oldType, newType); + } + + /** + * @see NodeServicePolicies.BeforeSetNodeTypePolicy#beforeSetNodeType(NodeRef, QName, QName) + */ + protected void invokeBeforeSetType(NodeRef nodeRef, QName oldType, QName newType) + { + if (ignorePolicy(nodeRef)) + { + return; + } + + // get qnames to invoke against + Set qnames = getTypeAndAspectQNames(nodeRef); + // execute policy for node type and aspects + NodeServicePolicies.BeforeSetNodeTypePolicy policy = beforeSetNodeTypeDelegate.get(nodeRef, qnames); + policy.beforeSetNodeType(nodeRef, oldType, newType); + } + /** * @see NodeServicePolicies.OnUpdateProperties#onUpdatePropertiesPolicy(NodeRef, Map, Map) */ diff --git a/source/java/org/alfresco/repo/node/NodeRefPropertyMethodInterceptor.java b/source/java/org/alfresco/repo/node/NodeRefPropertyMethodInterceptor.java index 229f7922ca..81a4320453 100644 --- a/source/java/org/alfresco/repo/node/NodeRefPropertyMethodInterceptor.java +++ b/source/java/org/alfresco/repo/node/NodeRefPropertyMethodInterceptor.java @@ -154,7 +154,7 @@ public class NodeRefPropertyMethodInterceptor implements MethodInterceptor return invocation.proceed(); } } - else if (methodName.equals("createNode") & (args.length == 5)) + else if (methodName.equals("createNode") && (args.length == 5)) { if (filterOnSet) { diff --git a/source/java/org/alfresco/repo/node/NodeServicePolicies.java b/source/java/org/alfresco/repo/node/NodeServicePolicies.java index 81c714f8e3..06bb39ea0a 100644 --- a/source/java/org/alfresco/repo/node/NodeServicePolicies.java +++ b/source/java/org/alfresco/repo/node/NodeServicePolicies.java @@ -311,4 +311,30 @@ public interface NodeServicePolicies */ public void onDeleteAssociation(AssociationRef nodeAssocRef); } + + public interface BeforeSetNodeTypePolicy extends ClassPolicy + { + public static final QName QNAME = QName.createQName(NamespaceService.ALFRESCO_URI, "beforeSetNodeType"); + /** + * Called before the type of a node is set explicitly. + * + * @param nodeRef the node having its type set. + * @param oldType the current type of the node. + * @param newType the type the node will be given. + */ + public void beforeSetNodeType(NodeRef nodeRef, QName oldType, QName newType); + } + + public interface OnSetNodeTypePolicy extends ClassPolicy + { + public static final QName QNAME = QName.createQName(NamespaceService.ALFRESCO_URI, "onSetNodeType"); + /** + * Called after the type of a node is set explicitly. + * + * @param nodeRef the node that has had its type set. + * @param oldType the previous type of the node. + * @param newType the type the node has been given. + */ + public void onSetNodeType(NodeRef nodeRef, QName oldType, QName newType); + } } diff --git a/source/java/org/alfresco/repo/node/NodeServiceTest.java b/source/java/org/alfresco/repo/node/NodeServiceTest.java index 8fea368d03..8b57bb6255 100644 --- a/source/java/org/alfresco/repo/node/NodeServiceTest.java +++ b/source/java/org/alfresco/repo/node/NodeServiceTest.java @@ -18,6 +18,9 @@ */ package org.alfresco.repo.node; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + import java.io.Serializable; import java.util.Collection; import java.util.Collections; @@ -34,7 +37,18 @@ import org.alfresco.repo.cache.SimpleCache; import org.alfresco.repo.domain.node.Node; import org.alfresco.repo.domain.node.NodeVersionKey; import org.alfresco.repo.domain.node.ParentAssocsInfo; +import org.alfresco.repo.node.NodeServicePolicies.BeforeCreateNodePolicy; +import org.alfresco.repo.node.NodeServicePolicies.BeforeSetNodeTypePolicy; +import org.alfresco.repo.node.NodeServicePolicies.BeforeUpdateNodePolicy; +import org.alfresco.repo.node.NodeServicePolicies.OnCreateChildAssociationPolicy; +import org.alfresco.repo.node.NodeServicePolicies.OnCreateNodePolicy; +import org.alfresco.repo.node.NodeServicePolicies.OnSetNodeTypePolicy; +import org.alfresco.repo.node.NodeServicePolicies.OnUpdateNodePolicy; +import org.alfresco.repo.node.NodeServicePolicies.OnUpdatePropertiesPolicy; import org.alfresco.repo.policy.BehaviourFilter; +import org.alfresco.repo.policy.JavaBehaviour; +import org.alfresco.repo.policy.Policy; +import org.alfresco.repo.policy.PolicyComponent; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.ServiceRegistry; @@ -52,6 +66,7 @@ import org.alfresco.service.namespace.QName; import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.ApplicationContextHelper; import org.alfresco.util.GUID; +import org.alfresco.util.PropertyMap; import org.springframework.context.ApplicationContext; import org.springframework.extensions.surf.util.I18NUtil; @@ -74,6 +89,7 @@ public class NodeServiceTest extends TestCase protected ServiceRegistry serviceRegistry; protected NodeService nodeService; private TransactionService txnService; + private PolicyComponent policyComponent; private SimpleCache nodesCache; private SimpleCache propsCache; private SimpleCache aspectsCache; @@ -91,6 +107,7 @@ public class NodeServiceTest extends TestCase serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY); nodeService = serviceRegistry.getNodeService(); txnService = serviceRegistry.getTransactionService(); + policyComponent = (PolicyComponent) ctx.getBean("policyComponent"); // Get the caches for later testing nodesCache = (SimpleCache) ctx.getBean("node.nodesSharedCache"); @@ -915,4 +932,111 @@ public class NodeServiceTest extends TestCase assertTrue("Aspects be carried", nodeAspectsEight == nodeAspectsSeven); assertTrue("Parent assocs must be carried", nodeParentAssocsEight == nodeParentAssocsSeven); } + + public void testCreateNodePolicies() + { + // Create and bind the mock behaviours... + OnCreateNodePolicy onCreateNodePolicy = createClassPolicy( + OnCreateNodePolicy.class, + OnCreateNodePolicy.QNAME, + ContentModel.TYPE_CONTENT); + + BeforeCreateNodePolicy beforeCreateNodePolicy = createClassPolicy( + BeforeCreateNodePolicy.class, + BeforeCreateNodePolicy.QNAME, + ContentModel.TYPE_CONTENT); + + OnCreateChildAssociationPolicy onCreateChildAssociationPolicy = createAssocPolicy( + OnCreateChildAssociationPolicy.class, + OnCreateChildAssociationPolicy.QNAME, + ContentModel.TYPE_STOREROOT); + + OnUpdatePropertiesPolicy onUpdatePropertiesPolicy = createClassPolicy( + OnUpdatePropertiesPolicy.class, + OnUpdatePropertiesPolicy.QNAME, + ContentModel.TYPE_CONTENT); + + // Create a node - this should result in the behaviours firing. + NodeRef newNodeRef = nodeService.createNode( + this.rootNodeRef, + ContentModel.ASSOC_CHILDREN, + ContentModel.ASSOC_CHILDREN, + ContentModel.TYPE_CONTENT, + PropertyMap.EMPTY_MAP).getChildRef(); + + Map propsAfter = nodeService.getProperties(newNodeRef); + ChildAssociationRef childAssocRef = nodeService.getPrimaryParent(newNodeRef); + + // Check the behaviours fired as expected... + verify(beforeCreateNodePolicy).beforeCreateNode( + rootNodeRef, + ContentModel.ASSOC_CHILDREN, + ContentModel.ASSOC_CHILDREN, + ContentModel.TYPE_CONTENT); + verify(onCreateNodePolicy).onCreateNode(childAssocRef); + verify(onCreateChildAssociationPolicy).onCreateChildAssociation(childAssocRef, true); + verify(onUpdatePropertiesPolicy).onUpdateProperties(newNodeRef, PropertyMap.EMPTY_MAP, propsAfter); + } + + public void testSetNodeTypePolicies() + { + // Create a node (before behaviours are attached) + NodeRef nodeRef = nodeService.createNode( + this.rootNodeRef, + ContentModel.ASSOC_CHILDREN, + ContentModel.ASSOC_CHILDREN, + ContentModel.TYPE_CONTENT, + new HashMap(0)).getChildRef(); + + // Create and bind the mock behaviours... + BeforeUpdateNodePolicy beforeUpdatePolicy = createClassPolicy( + BeforeUpdateNodePolicy.class, + BeforeUpdateNodePolicy.QNAME, + ContentModel.TYPE_CONTENT); + + OnUpdateNodePolicy onUpdatePolicy = createClassPolicy( + OnUpdateNodePolicy.class, + OnUpdateNodePolicy.QNAME, + ContentModel.TYPE_FOLDER); + + BeforeSetNodeTypePolicy beforeSetNodeTypePolicy = createClassPolicy( + BeforeSetNodeTypePolicy.class, + BeforeSetNodeTypePolicy.QNAME, + ContentModel.TYPE_CONTENT); + + OnSetNodeTypePolicy onSetNodeTypePolicy = createClassPolicy( + OnSetNodeTypePolicy.class, + OnSetNodeTypePolicy.QNAME, + ContentModel.TYPE_FOLDER); + + // Set the type of the new node - this should trigger the correct behaviours. + nodeService.setType(nodeRef, ContentModel.TYPE_FOLDER); + + // Check the behaviours fired as expected... + verify(beforeUpdatePolicy).beforeUpdateNode(nodeRef); + verify(onUpdatePolicy).onUpdateNode(nodeRef); + verify(beforeSetNodeTypePolicy).beforeSetNodeType(nodeRef, ContentModel.TYPE_CONTENT, ContentModel.TYPE_FOLDER); + verify(onSetNodeTypePolicy).onSetNodeType(nodeRef, ContentModel.TYPE_CONTENT, ContentModel.TYPE_FOLDER); + } + + private T createClassPolicy(Class policyInterface, QName policyQName, QName triggerOnClass) + { + T policy = mock(policyInterface); + policyComponent.bindClassBehaviour( + policyQName, + triggerOnClass, + new JavaBehaviour(policy, policyQName.getLocalName())); + return policy; + } + + + private T createAssocPolicy(Class policyInterface, QName policyQName, QName triggerOnClass) + { + T policy = mock(policyInterface); + policyComponent.bindAssociationBehaviour( + policyQName, + triggerOnClass, + new JavaBehaviour(policy, policyQName.getLocalName())); + return policy; + } } diff --git a/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java b/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java index 3e119d98d2..647ed5d4c6 100644 --- a/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java +++ b/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java @@ -769,6 +769,8 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl // Invoke policies invokeBeforeUpdateNode(nodeRef); + QName oldType = nodeDAO.getNodeType(nodePair.getFirst()); + invokeBeforeSetType(nodeRef, oldType, typeQName); // Set the type boolean updatedNode = nodeDAO.updateNode(nodePair.getFirst(), typeQName, null); @@ -781,6 +783,8 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl { // Invoke policies invokeOnUpdateNode(nodeRef); + invokeOnSetType(nodeRef, oldType, typeQName); + // Index nodeIndexer.indexUpdateNode(nodeRef); } @@ -834,6 +838,16 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl } } + /** + * @see Node#countChildAssocs() + */ + public int countChildAssocs(NodeRef nodeRef, boolean isPrimary) throws InvalidNodeRefException + { + final Pair nodePair = getNodePairNotNull(nodeRef); + final Long nodeId = nodePair.getFirst(); + return nodeDAO.countChildAssocsByParent(nodeId, isPrimary); + } + public void removeAspect(NodeRef nodeRef, QName aspectTypeQName) throws InvalidNodeRefException, InvalidAspectException { diff --git a/source/java/org/alfresco/repo/node/index/AbstractReindexComponent.java b/source/java/org/alfresco/repo/node/index/AbstractReindexComponent.java index a43fdf5e67..c276195615 100644 --- a/source/java/org/alfresco/repo/node/index/AbstractReindexComponent.java +++ b/source/java/org/alfresco/repo/node/index/AbstractReindexComponent.java @@ -785,12 +785,6 @@ public abstract class AbstractReindexComponent implements IndexRecovery List> nodePairs = new ArrayList>(nodeStatuses.size()); for (NodeRef.Status nodeStatus : nodeStatuses) { - if (nodeStatus == null) - { - // it's not there any more - continue; - } - ChildAssociationRef parent = nodeStatus.isDeleted() ? null : nodeService.getPrimaryParent(nodeStatus.getNodeRef()); nodePairs.add(new Pair(nodeStatus, parent)); } diff --git a/source/java/org/alfresco/repo/publishing/ChannelImplTest.java b/source/java/org/alfresco/repo/publishing/ChannelImplTest.java index 4a85669617..84b62ab698 100644 --- a/source/java/org/alfresco/repo/publishing/ChannelImplTest.java +++ b/source/java/org/alfresco/repo/publishing/ChannelImplTest.java @@ -25,6 +25,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import junit.framework.TestCase; +import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.repository.NodeRef; /** @@ -33,7 +34,15 @@ import org.alfresco.service.cmr.repository.NodeRef; */ public class ChannelImplTest extends TestCase { - public void testUpdateStatus() throws Exception + ServiceRegistry serviceRegMock; + + @Override + protected void setUp() throws Exception { + super.setUp(); + serviceRegMock = mock(ServiceRegistry.class); + } + + public void testUpdateStatus() throws Exception { int maxLength = 30; AbstractChannelType channelType = mockChannelType(maxLength); @@ -42,7 +51,7 @@ public class ChannelImplTest extends TestCase when(helper.getChannelProperties(any(NodeRef.class))).thenReturn(null); NodeRef node = new NodeRef("test://channel/node"); - ChannelImpl channel = new ChannelImpl(null, channelType, node, "Name", helper, null); + ChannelImpl channel = new ChannelImpl(serviceRegMock, channelType, node, "Name", helper, null); String msg = "Here is a message"; channel.sendStatusUpdate(msg, null); @@ -58,7 +67,7 @@ public class ChannelImplTest extends TestCase when(helper.getChannelProperties(any(NodeRef.class))).thenReturn(null); NodeRef node = new NodeRef("test://channel/node"); - ChannelImpl channel = new ChannelImpl(null, channelType, node, "Name", helper, null); + ChannelImpl channel = new ChannelImpl(serviceRegMock, channelType, node, "Name", helper, null); String msg = "Here is a much longer message to truncate."; String expMsg = msg.substring(0, maxLength); @@ -75,7 +84,7 @@ public class ChannelImplTest extends TestCase when(helper.getChannelProperties(any(NodeRef.class))).thenReturn(null); NodeRef node = new NodeRef("test://channel/node"); - ChannelImpl channel = new ChannelImpl(null, channelType, node, "Name", helper, null); + ChannelImpl channel = new ChannelImpl(serviceRegMock, channelType, node, "Name", helper, null); String nodeUrl ="http://foo/bar"; int endpoint = maxLength - nodeUrl.length(); @@ -93,7 +102,7 @@ public class ChannelImplTest extends TestCase when(helper.getChannelProperties(any(NodeRef.class))).thenReturn(null); NodeRef node = new NodeRef("test://channel/node"); - ChannelImpl channel = new ChannelImpl(null, channelType, node, "Name", helper, null); + ChannelImpl channel = new ChannelImpl(serviceRegMock, channelType, node, "Name", helper, null); String nodeUrl ="http://foo/bar"; String msg = "Here is a much longer message to truncate."; diff --git a/source/java/org/alfresco/repo/search/AVMSnapShotTriggeredIndexingMethodInterceptorImpl.java b/source/java/org/alfresco/repo/search/AVMSnapShotTriggeredIndexingMethodInterceptorImpl.java index ad18d6907a..77cdd6b37b 100644 --- a/source/java/org/alfresco/repo/search/AVMSnapShotTriggeredIndexingMethodInterceptorImpl.java +++ b/source/java/org/alfresco/repo/search/AVMSnapShotTriggeredIndexingMethodInterceptorImpl.java @@ -27,6 +27,7 @@ import java.util.regex.Pattern; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.repo.avm.AVMNodeConverter; import org.alfresco.repo.domain.PropertyValue; +import org.alfresco.repo.management.subsystems.SwitchableApplicationContextFactory; import org.alfresco.repo.search.impl.lucene.AVMLuceneIndexer; import org.alfresco.service.cmr.avm.AVMService; import org.alfresco.service.cmr.avm.AVMStoreDescriptor; @@ -60,6 +61,8 @@ public class AVMSnapShotTriggeredIndexingMethodInterceptorImpl implements AVMSna private Map modeCache = new HashMap(); private List indexingDefinitions = new ArrayList(); + + private SwitchableApplicationContextFactory searchApplicationContextFactory; /* (non-Javadoc) * @see org.alfresco.repo.search.AVMSnapShotTriggeredIndexingMethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation) @@ -162,6 +165,28 @@ public class AVMSnapShotTriggeredIndexingMethodInterceptorImpl implements AVMSna } } + + + /** + * @param searchApplicationContextFactory the searchApplicationContextFactory to set + */ + public void setSearchApplicationContextFactory(SwitchableApplicationContextFactory searchApplicationContextFactory) + { + this.searchApplicationContextFactory = searchApplicationContextFactory; + } + + + + /** + * @return the searchApplicationContextFactory + */ + public SwitchableApplicationContextFactory getSearchApplicationContextFactory() + { + return searchApplicationContextFactory; + } + + + /* (non-Javadoc) * @see org.alfresco.repo.search.AVMSnapShotTriggeredIndexingMethodInterceptor#setAvmService(org.alfresco.service.cmr.avm.AVMService) */ @@ -203,6 +228,8 @@ public class AVMSnapShotTriggeredIndexingMethodInterceptorImpl implements AVMSna } } + + /* (non-Javadoc) * @see org.alfresco.repo.search.AVMSnapShotTriggeredIndexingMethodInterceptor#setDefaultMode(org.alfresco.repo.search.IndexMode) */ @@ -534,7 +561,18 @@ public class AVMSnapShotTriggeredIndexingMethodInterceptorImpl implements AVMSna AVMLuceneIndexer avmIndexer = (AVMLuceneIndexer) indexer; return avmIndexer; } - return null; + else + { + if(searchApplicationContextFactory.getCurrentSourceBeanName().equals("solr")) + { + throw new AlfrescoRuntimeException("No AVM Indexer available (AVM is not supported with SOLR"); + //return null; + } + else + { + return null; + } + } } /* (non-Javadoc) @@ -550,5 +588,13 @@ public class AVMSnapShotTriggeredIndexingMethodInterceptorImpl implements AVMSna AVMLuceneIndexer avmIndexer = (AVMLuceneIndexer) indexer; avmIndexer.deleteIndex(storeRef); } + else + { + if(searchApplicationContextFactory.getCurrentSourceBeanName().equals("solr")) + { + throw new AlfrescoRuntimeException("No AVM Indexer available (AVM is not supported with SOLR"); + } + //else nothing to do + } } } diff --git a/source/java/org/alfresco/repo/search/impl/lucene/LuceneAlfrescoLuceneQueryLanguage.java b/source/java/org/alfresco/repo/search/impl/lucene/LuceneAlfrescoLuceneQueryLanguage.java index 2ab15de706..a182d8090d 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/LuceneAlfrescoLuceneQueryLanguage.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/LuceneAlfrescoLuceneQueryLanguage.java @@ -1,254 +1,254 @@ -/* - * 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.search.impl.lucene; - -import java.io.IOException; -import java.util.Locale; - -import org.alfresco.repo.dictionary.IndexTokenisationMode; -import org.alfresco.repo.search.EmptyResultSet; -import org.alfresco.repo.search.SearcherException; -import org.alfresco.repo.search.impl.lucene.analysis.DateTimeAnalyser; -import org.alfresco.repo.search.results.SortedResultSet; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.service.cmr.dictionary.PropertyDefinition; -import org.alfresco.service.cmr.search.ResultSet; -import org.alfresco.service.cmr.search.SearchParameters; -import org.alfresco.service.cmr.search.SearchService; -import org.alfresco.service.namespace.QName; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.lucene.index.IndexReader.FieldOption; -import org.apache.lucene.queryParser.ParseException; -import org.apache.lucene.queryParser.QueryParser.Operator; -import org.apache.lucene.search.Hits; -import org.apache.lucene.search.Query; -import org.apache.lucene.search.Sort; -import org.apache.lucene.search.SortField; - -/** - * Alfresco FTS Query language support - * - * @author andyh - */ -public class LuceneAlfrescoLuceneQueryLanguage extends AbstractLuceneQueryLanguage -{ - static Log s_logger = LogFactory.getLog(LuceneAlfrescoLuceneQueryLanguage.class); - - public LuceneAlfrescoLuceneQueryLanguage() - { - this.setName(SearchService.LANGUAGE_LUCENE); - } - - public ResultSet executeQuery(SearchParameters searchParameters, ADMLuceneSearcherImpl admLuceneSearcher) - { - try - { - - Operator defaultOperator; - if (searchParameters.getDefaultOperator() == SearchParameters.AND) - { - defaultOperator = LuceneQueryParser.AND_OPERATOR; - } - else - { - defaultOperator = LuceneQueryParser.OR_OPERATOR; - } - - ClosingIndexSearcher searcher = admLuceneSearcher.getClosingIndexSearcher(); - Query query = LuceneQueryParser.parse(searchParameters.getQuery(), searchParameters.getDefaultFieldName(), new LuceneAnalyser(admLuceneSearcher.getDictionaryService(), - searchParameters.getMlAnalaysisMode() == null ? admLuceneSearcher.getLuceneConfig().getDefaultMLSearchAnalysisMode() : searchParameters.getMlAnalaysisMode()), - admLuceneSearcher.getNamespacePrefixResolver(), admLuceneSearcher.getDictionaryService(), admLuceneSearcher.getTenantService(), defaultOperator, searchParameters, admLuceneSearcher.getLuceneConfig().getDefaultMLSearchAnalysisMode(), searcher.getIndexReader()); - if (s_logger.isDebugEnabled()) - { - s_logger.debug("Query is " + query.toString()); - } - if (searcher == null) - { - // no index return an empty result set - return new EmptyResultSet(); - } - - Hits hits; - - boolean requiresDateTimePostSort = false; - SortField[] fields = new SortField[searchParameters.getSortDefinitions().size()]; - - if (searchParameters.getSortDefinitions().size() > 0) - { - int index = 0; - for (SearchParameters.SortDefinition sd : searchParameters.getSortDefinitions()) - { - switch (sd.getSortType()) - { - case FIELD: - Locale sortLocale = searchParameters.getSortLocale(); - String field = sd.getField(); - if (field.startsWith("@")) - { - field = admLuceneSearcher.expandAttributeFieldName(field); - PropertyDefinition propertyDef = admLuceneSearcher.getDictionaryService().getProperty(QName.createQName(field.substring(1))); - - if(propertyDef == null) - { - if(field.endsWith(".size")) - { - propertyDef = admLuceneSearcher.getDictionaryService().getProperty(QName.createQName(field.substring(1, field.length()-5))); - if (!propertyDef.getDataType().getName().equals(DataTypeDefinition.CONTENT)) - { - throw new SearcherException("Order for .size only supported on content properties"); - } - } - else if (field.endsWith(".mimetype")) - { - propertyDef = admLuceneSearcher.getDictionaryService().getProperty(QName.createQName(field.substring(1, field.length()-9))); - if (!propertyDef.getDataType().getName().equals(DataTypeDefinition.CONTENT)) - { - throw new SearcherException("Order for .mimetype only supported on content properties"); - } - } - else - { - // nothing - } - } - else - { - if (propertyDef.getDataType().getName().equals(DataTypeDefinition.CONTENT)) - { - throw new SearcherException("Order on content properties is not curently supported"); - } - - else if (propertyDef.getDataType().getName().equals(DataTypeDefinition.TEXT)) - { - if(propertyDef.getIndexTokenisationMode() == IndexTokenisationMode.FALSE) - { - // use field as is - } - else - { - - String noLocalField = field+".no_locale"; - for (Object current : searcher.getIndexReader().getFieldNames(FieldOption.INDEXED)) - { - String currentString = (String) current; - if (currentString.equals(noLocalField)) - { - field = noLocalField; - } - } - - if(!field.endsWith(".no_locale")) - { - field = admLuceneSearcher.findSortField(searchParameters, searcher, field, sortLocale); - } - } - } - - else if (propertyDef.getDataType().getName().equals(DataTypeDefinition.MLTEXT)) - { - - field = admLuceneSearcher.findSortField(searchParameters, searcher, field, sortLocale); - - } - else if (propertyDef.getDataType().getName().equals(DataTypeDefinition.DATETIME)) - { - DataTypeDefinition dataType = propertyDef.getDataType(); - String analyserClassName = propertyDef.resolveAnalyserClassName(); - if (analyserClassName.equals(DateTimeAnalyser.class.getCanonicalName())) - { - switch (propertyDef.getIndexTokenisationMode()) - { - case TRUE: - requiresDateTimePostSort = true; - break; - case BOTH: - field = field + ".sort"; - break; - case FALSE: - // Should be able to sort on actual field OK - break; - } - } - else - { - requiresDateTimePostSort = true; - } - } - } - } - - if (LuceneUtils.fieldHasTerm(searcher.getReader(), field)) - { - fields[index++] = new SortField(field, sortLocale, !sd.isAscending()); - } - else - { - fields[index++] = new SortField(null, SortField.DOC, !sd.isAscending()); - } - break; - case DOCUMENT: - fields[index++] = new SortField(null, SortField.DOC, !sd.isAscending()); - break; - case SCORE: - // Score is naturally high to low -ie desc - fields[index++] = new SortField(null, SortField.SCORE, sd.isAscending()); - break; - } - - } - } - - hits = searcher.search(query); - - boolean postSort = false;; - if(fields.length > 0) - { - postSort = searchParameters.usePostSort(hits.length(), admLuceneSearcher.getLuceneConfig().getUseInMemorySort(), admLuceneSearcher.getLuceneConfig().getMaxRawResultSetSizeForInMemorySort()); - if(postSort == false) - { - hits = searcher.search(query, new Sort(fields)); - } - } - - ResultSet answer; - ResultSet result = new LuceneResultSet(hits, searcher, admLuceneSearcher.getNodeService(), admLuceneSearcher.getTenantService(), searchParameters, admLuceneSearcher.getLuceneConfig()); - if(postSort || (admLuceneSearcher.getLuceneConfig().getPostSortDateTime() && requiresDateTimePostSort)) - { - ResultSet sorted = new SortedResultSet(result, admLuceneSearcher.getNodeService(), searchParameters.getSortDefinitions(), admLuceneSearcher.getNamespacePrefixResolver(), admLuceneSearcher.getDictionaryService(), searchParameters.getSortLocale()); - answer = sorted; - } - else - { - answer = result; - } - ResultSet rs = new PagingLuceneResultSet(answer, searchParameters, admLuceneSearcher.getNodeService()); - return rs; - } - catch (ParseException e) - { - throw new SearcherException("Failed to parse query: " + searchParameters.getQuery(), e); - } - catch (IOException e) - { - throw new SearcherException("IO exception during search", e); - } - } - -} +/* + * 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.search.impl.lucene; + +import java.io.IOException; +import java.util.Locale; + +import org.alfresco.repo.dictionary.IndexTokenisationMode; +import org.alfresco.repo.search.EmptyResultSet; +import org.alfresco.repo.search.SearcherException; +import org.alfresco.repo.search.impl.lucene.analysis.DateTimeAnalyser; +import org.alfresco.repo.search.results.SortedResultSet; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.service.cmr.search.ResultSet; +import org.alfresco.service.cmr.search.SearchParameters; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.namespace.QName; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.lucene.index.IndexReader.FieldOption; +import org.apache.lucene.queryParser.ParseException; +import org.apache.lucene.queryParser.QueryParser.Operator; +import org.apache.lucene.search.Hits; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.Sort; +import org.apache.lucene.search.SortField; + +/** + * Alfresco FTS Query language support + * + * @author andyh + */ +public class LuceneAlfrescoLuceneQueryLanguage extends AbstractLuceneQueryLanguage +{ + static Log s_logger = LogFactory.getLog(LuceneAlfrescoLuceneQueryLanguage.class); + + public LuceneAlfrescoLuceneQueryLanguage() + { + this.setName(SearchService.LANGUAGE_LUCENE); + } + + public ResultSet executeQuery(SearchParameters searchParameters, ADMLuceneSearcherImpl admLuceneSearcher) + { + try + { + + Operator defaultOperator; + if (searchParameters.getDefaultOperator() == SearchParameters.AND) + { + defaultOperator = LuceneQueryParser.AND_OPERATOR; + } + else + { + defaultOperator = LuceneQueryParser.OR_OPERATOR; + } + + ClosingIndexSearcher searcher = admLuceneSearcher.getClosingIndexSearcher(); + if (searcher == null) + { + // no index return an empty result set + return new EmptyResultSet(); + } + Query query = LuceneQueryParser.parse(searchParameters.getQuery(), searchParameters.getDefaultFieldName(), new LuceneAnalyser(admLuceneSearcher.getDictionaryService(), + searchParameters.getMlAnalaysisMode() == null ? admLuceneSearcher.getLuceneConfig().getDefaultMLSearchAnalysisMode() : searchParameters.getMlAnalaysisMode()), + admLuceneSearcher.getNamespacePrefixResolver(), admLuceneSearcher.getDictionaryService(), admLuceneSearcher.getTenantService(), defaultOperator, searchParameters, admLuceneSearcher.getLuceneConfig().getDefaultMLSearchAnalysisMode(), searcher.getIndexReader()); + if (s_logger.isDebugEnabled()) + { + s_logger.debug("Query is " + query.toString()); + } + + Hits hits; + + boolean requiresDateTimePostSort = false; + SortField[] fields = new SortField[searchParameters.getSortDefinitions().size()]; + + if (searchParameters.getSortDefinitions().size() > 0) + { + int index = 0; + for (SearchParameters.SortDefinition sd : searchParameters.getSortDefinitions()) + { + switch (sd.getSortType()) + { + case FIELD: + Locale sortLocale = searchParameters.getSortLocale(); + String field = sd.getField(); + if (field.startsWith("@")) + { + field = admLuceneSearcher.expandAttributeFieldName(field); + PropertyDefinition propertyDef = admLuceneSearcher.getDictionaryService().getProperty(QName.createQName(field.substring(1))); + + if(propertyDef == null) + { + if(field.endsWith(".size")) + { + propertyDef = admLuceneSearcher.getDictionaryService().getProperty(QName.createQName(field.substring(1, field.length()-5))); + if (!propertyDef.getDataType().getName().equals(DataTypeDefinition.CONTENT)) + { + throw new SearcherException("Order for .size only supported on content properties"); + } + } + else if (field.endsWith(".mimetype")) + { + propertyDef = admLuceneSearcher.getDictionaryService().getProperty(QName.createQName(field.substring(1, field.length()-9))); + if (!propertyDef.getDataType().getName().equals(DataTypeDefinition.CONTENT)) + { + throw new SearcherException("Order for .mimetype only supported on content properties"); + } + } + else + { + // nothing + } + } + else + { + if (propertyDef.getDataType().getName().equals(DataTypeDefinition.CONTENT)) + { + throw new SearcherException("Order on content properties is not curently supported"); + } + + else if (propertyDef.getDataType().getName().equals(DataTypeDefinition.TEXT)) + { + if(propertyDef.getIndexTokenisationMode() == IndexTokenisationMode.FALSE) + { + // use field as is + } + else + { + + String noLocalField = field+".no_locale"; + for (Object current : searcher.getIndexReader().getFieldNames(FieldOption.INDEXED)) + { + String currentString = (String) current; + if (currentString.equals(noLocalField)) + { + field = noLocalField; + } + } + + if(!field.endsWith(".no_locale")) + { + field = admLuceneSearcher.findSortField(searchParameters, searcher, field, sortLocale); + } + } + } + + else if (propertyDef.getDataType().getName().equals(DataTypeDefinition.MLTEXT)) + { + + field = admLuceneSearcher.findSortField(searchParameters, searcher, field, sortLocale); + + } + else if (propertyDef.getDataType().getName().equals(DataTypeDefinition.DATETIME)) + { + DataTypeDefinition dataType = propertyDef.getDataType(); + String analyserClassName = propertyDef.resolveAnalyserClassName(); + if (analyserClassName.equals(DateTimeAnalyser.class.getCanonicalName())) + { + switch (propertyDef.getIndexTokenisationMode()) + { + case TRUE: + requiresDateTimePostSort = true; + break; + case BOTH: + field = field + ".sort"; + break; + case FALSE: + // Should be able to sort on actual field OK + break; + } + } + else + { + requiresDateTimePostSort = true; + } + } + } + } + + if (LuceneUtils.fieldHasTerm(searcher.getReader(), field)) + { + fields[index++] = new SortField(field, sortLocale, !sd.isAscending()); + } + else + { + fields[index++] = new SortField(null, SortField.DOC, !sd.isAscending()); + } + break; + case DOCUMENT: + fields[index++] = new SortField(null, SortField.DOC, !sd.isAscending()); + break; + case SCORE: + // Score is naturally high to low -ie desc + fields[index++] = new SortField(null, SortField.SCORE, sd.isAscending()); + break; + } + + } + } + + hits = searcher.search(query); + + boolean postSort = false;; + if(fields.length > 0) + { + postSort = searchParameters.usePostSort(hits.length(), admLuceneSearcher.getLuceneConfig().getUseInMemorySort(), admLuceneSearcher.getLuceneConfig().getMaxRawResultSetSizeForInMemorySort()); + if(postSort == false) + { + hits = searcher.search(query, new Sort(fields)); + } + } + + ResultSet answer; + ResultSet result = new LuceneResultSet(hits, searcher, admLuceneSearcher.getNodeService(), admLuceneSearcher.getTenantService(), searchParameters, admLuceneSearcher.getLuceneConfig()); + if(postSort || (admLuceneSearcher.getLuceneConfig().getPostSortDateTime() && requiresDateTimePostSort)) + { + ResultSet sorted = new SortedResultSet(result, admLuceneSearcher.getNodeService(), searchParameters.getSortDefinitions(), admLuceneSearcher.getNamespacePrefixResolver(), admLuceneSearcher.getDictionaryService(), searchParameters.getSortLocale()); + answer = sorted; + } + else + { + answer = result; + } + ResultSet rs = new PagingLuceneResultSet(answer, searchParameters, admLuceneSearcher.getNodeService()); + return rs; + } + catch (ParseException e) + { + throw new SearcherException("Failed to parse query: " + searchParameters.getQuery(), e); + } + catch (IOException e) + { + throw new SearcherException("IO exception during search", e); + } + } + +} diff --git a/source/java/org/alfresco/repo/search/impl/lucene/index/IndexInfo.java b/source/java/org/alfresco/repo/search/impl/lucene/index/IndexInfo.java index bce6076ba1..c0378416e5 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/index/IndexInfo.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/index/IndexInfo.java @@ -1896,18 +1896,9 @@ public class IndexInfo implements IndexMonitor IndexEntry entry = indexEntries.get(id); if (entry == null) { - // We could be retrying - see if the index reader is known or the directory is left - if (referenceCountingReadOnlyIndexReaders.get(id) == null) - { - File location = new File(indexDirectory, id).getCanonicalFile(); - if (!location.exists()) - { - throw new IndexerException("Unknown transaction " + id); - } - } - clearOldReaders(); cleaner.schedule(); + throw new IndexerException("Unknown transaction " + id); } if (TransactionStatus.COMMITTED.follows(entry.getStatus())) @@ -2007,18 +1998,9 @@ public class IndexInfo implements IndexMonitor IndexEntry entry = indexEntries.get(id); if (entry == null) { - // We could be retrying - see if the index reader is known or the directory is left - if (referenceCountingReadOnlyIndexReaders.get(id) == null) - { - File location = new File(indexDirectory, id).getCanonicalFile(); - if (!location.exists()) - { - throw new IndexerException("Unknown transaction " + id); - } - } - clearOldReaders(); cleaner.schedule(); + throw new IndexerException("Unknown transaction " + id); } if (TransactionStatus.ROLLEDBACK.follows(entry.getStatus())) @@ -2059,18 +2041,9 @@ public class IndexInfo implements IndexMonitor IndexEntry entry = indexEntries.get(id); if (entry == null) { - // We could be retrying - see if the index reader is known or the directory is left - if (referenceCountingReadOnlyIndexReaders.get(id) == null) - { - File location = new File(indexDirectory, id).getCanonicalFile(); - if (!location.exists()) - { - throw new IndexerException("Unknown transaction " + id); - } - } - clearOldReaders(); cleaner.schedule(); + throw new IndexerException("Unknown transaction " + id); } if (TransactionStatus.DELETABLE.follows(entry.getStatus())) diff --git a/source/java/org/alfresco/repo/search/impl/lucene/index/ReferenceCounting.java b/source/java/org/alfresco/repo/search/impl/lucene/index/ReferenceCounting.java index 4f50a81033..0985a2447b 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/index/ReferenceCounting.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/index/ReferenceCounting.java @@ -19,7 +19,7 @@ package org.alfresco.repo.search.impl.lucene.index; import java.io.IOException; -import java.util.List; +import java.util.Deque; /** * Reference counting and caching for read only index access. @@ -34,7 +34,7 @@ public interface ReferenceCounting { public long getCreationTime(); - public List getReferences(); + public Deque getReferences(); /** * Get the number of references diff --git a/source/java/org/alfresco/repo/search/impl/lucene/index/ReferenceCountingReadOnlyIndexReaderFactory.java b/source/java/org/alfresco/repo/search/impl/lucene/index/ReferenceCountingReadOnlyIndexReaderFactory.java index 5200c98c45..d902f22b5c 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/index/ReferenceCountingReadOnlyIndexReaderFactory.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/index/ReferenceCountingReadOnlyIndexReaderFactory.java @@ -21,6 +21,7 @@ package org.alfresco.repo.search.impl.lucene.index; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Deque; import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -35,10 +36,10 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; -import org.apache.lucene.document.Field.Index; -import org.apache.lucene.document.Field.Store; import org.apache.lucene.document.FieldSelector; import org.apache.lucene.document.FieldSelectorResult; +import org.apache.lucene.document.Field.Index; +import org.apache.lucene.document.Field.Store; import org.apache.lucene.index.FilterIndexReader; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.Term; @@ -126,7 +127,7 @@ public class ReferenceCountingReadOnlyIndexReaderFactory private LuceneConfig config; private final long creationTime; - private final List references; + private final Deque references; static { @@ -168,7 +169,7 @@ public class ReferenceCountingReadOnlyIndexReaderFactory } @Override - public synchronized List getReferences() + public synchronized Deque getReferences() { return this.references; } @@ -218,7 +219,6 @@ public class ReferenceCountingReadOnlyIndexReaderFactory { s_logger.error("Invalid reference count for Reader " + id + " is " + refCount + " ... " + super.toString()); } - references.add(new Exception(this.refCount + ": " + in.toString())); } private void closeIfRequired() throws IOException @@ -237,6 +237,11 @@ public class ReferenceCountingReadOnlyIndexReaderFactory super.decRef(); wrapper_closed = true; + + if (!references.isEmpty()) + { + references.removeLast(); + } } else { @@ -293,6 +298,10 @@ public class ReferenceCountingReadOnlyIndexReaderFactory if (refCount > 0) { super.decRef(); + if (!references.isEmpty()) + { + references.removeLast(); + } } } diff --git a/source/java/org/alfresco/repo/search/impl/noindex/NoIndexSearchService.java b/source/java/org/alfresco/repo/search/impl/noindex/NoIndexSearchService.java index 21b50d9b74..29c447041c 100644 --- a/source/java/org/alfresco/repo/search/impl/noindex/NoIndexSearchService.java +++ b/source/java/org/alfresco/repo/search/impl/noindex/NoIndexSearchService.java @@ -19,6 +19,7 @@ package org.alfresco.repo.search.impl.noindex; import java.io.Serializable; +import java.util.Arrays; import java.util.List; import org.alfresco.error.StackTraceUtil; @@ -112,7 +113,7 @@ public class NoIndexSearchService implements SearchService { if (s_logger.isDebugEnabled()) { - s_logger.debug("query store = " + store + " language = " + language + " query = " + query + " queryParameterDefinitions = " + queryParameterDefinitions); + s_logger.debug("query store = " + store + " language = " + language + " query = " + query + " queryParameterDefinitions = " + Arrays.toString(queryParameterDefinitions)); } trace(); return new EmptyResultSet(); @@ -128,7 +129,7 @@ public class NoIndexSearchService implements SearchService { if (s_logger.isDebugEnabled()) { - s_logger.debug("query store = " + store + " queryId = " + queryId + " queryParameters = " + queryParameters); + s_logger.debug("query store = " + store + " queryId = " + queryId + " queryParameters = " + Arrays.toString(queryParameters)); } trace(); return new EmptyResultSet(); diff --git a/source/java/org/alfresco/repo/search/impl/solr/SolrChildApplicationContextFactory.java b/source/java/org/alfresco/repo/search/impl/solr/SolrChildApplicationContextFactory.java index bc2f395888..9e354d9801 100644 --- a/source/java/org/alfresco/repo/search/impl/solr/SolrChildApplicationContextFactory.java +++ b/source/java/org/alfresco/repo/search/impl/solr/SolrChildApplicationContextFactory.java @@ -159,4 +159,41 @@ public class SolrChildApplicationContextFactory extends ChildApplicationContextF super.setProperty(name, value); } + /* (non-Javadoc) + * @see org.alfresco.repo.management.subsystems.AbstractPropertyBackedBean#setProperties(java.util.Map) + */ + @Override + public void setProperties(Map properties) + { + // Remove any read only properties + HashMap propertiesToSet = new HashMap(properties); + if(propertiesToSet.containsKey(SolrChildApplicationContextFactory.ALFRESCO_ACTIVE)) + { + propertiesToSet.remove(SolrChildApplicationContextFactory.ALFRESCO_ACTIVE); + } + if(propertiesToSet.containsKey(SolrChildApplicationContextFactory.ALFRESCO_LAG)) + { + propertiesToSet.remove(SolrChildApplicationContextFactory.ALFRESCO_LAG); + } + if(propertiesToSet.containsKey(SolrChildApplicationContextFactory.ALFRESCO_LAG_DURATION)) + { + propertiesToSet.remove(SolrChildApplicationContextFactory.ALFRESCO_LAG_DURATION); + } + if(propertiesToSet.containsKey(SolrChildApplicationContextFactory.ARCHIVE_ACTIVE)) + { + propertiesToSet.remove(SolrChildApplicationContextFactory.ARCHIVE_ACTIVE); + } + if(propertiesToSet.containsKey(SolrChildApplicationContextFactory.ARCHIVE_LAG)) + { + propertiesToSet.remove(SolrChildApplicationContextFactory.ARCHIVE_LAG); + } + if(propertiesToSet.containsKey(SolrChildApplicationContextFactory.ARCHIVE_LAG_DURATION)) + { + propertiesToSet.remove(SolrChildApplicationContextFactory.ARCHIVE_LAG_DURATION); + } + + super.setProperties(propertiesToSet); + } + + } diff --git a/source/java/org/alfresco/repo/search/impl/solr/SolrQueryHTTPClient.java b/source/java/org/alfresco/repo/search/impl/solr/SolrQueryHTTPClient.java index 137b12703e..d9528910d4 100644 --- a/source/java/org/alfresco/repo/search/impl/solr/SolrQueryHTTPClient.java +++ b/source/java/org/alfresco/repo/search/impl/solr/SolrQueryHTTPClient.java @@ -30,6 +30,7 @@ import javax.servlet.http.HttpServletResponse; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.httpclient.HttpClientFactory; +import org.alfresco.repo.admin.RepositoryState; import org.alfresco.repo.search.impl.lucene.LuceneQueryParserException; import org.alfresco.repo.search.impl.lucene.SolrJSONResultSet; import org.alfresco.repo.tenant.TenantService; @@ -43,6 +44,7 @@ import org.alfresco.service.cmr.search.SearchParameters.FieldFacetMethod; import org.alfresco.service.cmr.search.SearchParameters.FieldFacetSort; import org.alfresco.service.cmr.search.SearchParameters.SortDefinition; import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.util.PropertyCheck; import org.apache.commons.codec.net.URLCodec; import org.apache.commons.httpclient.Header; import org.apache.commons.httpclient.HttpClient; @@ -82,14 +84,25 @@ public class SolrQueryHTTPClient private String baseUrl; private HttpClient httpClient; + private HttpClientFactory httpClientFactory; + private RepositoryState repositoryState; + public SolrQueryHTTPClient() { } public void init() { + PropertyCheck.mandatory(this, "NodeService", nodeService); + PropertyCheck.mandatory(this, "PermissionService", nodeService); + PropertyCheck.mandatory(this, "TenantService", nodeService); + PropertyCheck.mandatory(this, "LanguageMappings", nodeService); + PropertyCheck.mandatory(this, "StoreMappings", nodeService); + PropertyCheck.mandatory(this, "HttpClientFactory", nodeService); + PropertyCheck.mandatory(this, "RepositoryState", nodeService); + StringBuilder sb = new StringBuilder(); sb.append("/solr"); this.baseUrl = sb.toString(); @@ -100,6 +113,14 @@ public class SolrQueryHTTPClient httpClient.getState().setCredentials(new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT), new UsernamePasswordCredentials("admin", "admin")); } + /** + * @param repositoryState the repositoryState to set + */ + public void setRepositoryState(RepositoryState repositoryState) + { + this.repositoryState = repositoryState; + } + public void setHttpClientFactory(HttpClientFactory httpClientFactory) { this.httpClientFactory = httpClientFactory; @@ -132,6 +153,11 @@ public class SolrQueryHTTPClient public ResultSet executeQuery(SearchParameters searchParameters, String language) { + if(repositoryState.isBootstrapping()) + { + throw new AlfrescoRuntimeException("SOLR queries can not be executed while the repository is bootstrapping"); + } + try { URLCodec encoder = new URLCodec(); diff --git a/source/java/org/alfresco/repo/security/SecurityTestSuite.java b/source/java/org/alfresco/repo/security/SecurityTestSuite.java index f1b2c1f155..826de1e3e9 100644 --- a/source/java/org/alfresco/repo/security/SecurityTestSuite.java +++ b/source/java/org/alfresco/repo/security/SecurityTestSuite.java @@ -25,6 +25,7 @@ import junit.framework.TestSuite; import org.alfresco.repo.ownable.impl.OwnableServiceTest; import org.alfresco.repo.security.authentication.AuthenticationBootstrapTest; import org.alfresco.repo.security.authentication.AuthenticationTest; +import org.alfresco.repo.security.authentication.AuthorizationTest; import org.alfresco.repo.security.authentication.ChainingAuthenticationServiceTest; import org.alfresco.repo.security.authentication.NameBasedUserNameGeneratorTest; import org.alfresco.repo.security.authority.AuthorityServiceTest; @@ -75,6 +76,7 @@ public class SecurityTestSuite extends TestSuite // suite.addTestSuite(ChainingUserRegistrySynchronizerTest.class); suite.addTestSuite(OwnableServiceTest.class); suite.addTestSuite(ReadPermissionTest.class); + suite.addTestSuite(AuthorizationTest.class); suite.addTest(new JUnit4TestAdapter(HomeFolderProviderSynchronizerTest.class)); diff --git a/source/java/org/alfresco/repo/security/authentication/Authorization.java b/source/java/org/alfresco/repo/security/authentication/Authorization.java index 791dcd7a64..e7513ec64e 100644 --- a/source/java/org/alfresco/repo/security/authentication/Authorization.java +++ b/source/java/org/alfresco/repo/security/authentication/Authorization.java @@ -47,12 +47,18 @@ public class Authorization public Authorization(String authorization) { ParameterCheck.mandatoryString("authorization", authorization); + if (authorization.length() == 0) + { + throw new IllegalArgumentException("authorization does not consist of username and password"); + } + int idx = authorization.indexOf(':'); if (idx == -1) { setUser(null, authorization); - } else + } + else { setUser(authorization.substring(0, idx), authorization.substring(idx + 1)); } diff --git a/source/java/org/alfresco/repo/security/authentication/AuthorizationTest.java b/source/java/org/alfresco/repo/security/authentication/AuthorizationTest.java index 1a781761fd..6e7af7b709 100644 --- a/source/java/org/alfresco/repo/security/authentication/AuthorizationTest.java +++ b/source/java/org/alfresco/repo/security/authentication/AuthorizationTest.java @@ -41,7 +41,7 @@ public class AuthorizationTest extends TestCase } try { - new Authorization("username:password:invalid"); + new Authorization(""); fail(); } catch(IllegalArgumentException e) diff --git a/source/java/org/alfresco/repo/security/authority/script/ScriptAuthorityServiceTest.java b/source/java/org/alfresco/repo/security/authority/script/ScriptAuthorityServiceTest.java index 5b5928ca48..67f6f61623 100644 --- a/source/java/org/alfresco/repo/security/authority/script/ScriptAuthorityServiceTest.java +++ b/source/java/org/alfresco/repo/security/authority/script/ScriptAuthorityServiceTest.java @@ -19,6 +19,7 @@ package org.alfresco.repo.security.authority.script; import java.io.Serializable; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -206,9 +207,9 @@ public class ScriptAuthorityServiceTest extends TestCase if (group.getShortName().equals(GROUP_B)) groupB = group; if (group.getShortName().equals(GROUP_C)) groupC = group; } - assertNotNull(GROUP_A + " not found in " + groups, groupA); - assertNotNull(GROUP_B + " not found in " + groups, groupB); - assertNotNull(GROUP_C + " not found in " + groups, groupC); + assertNotNull(GROUP_A + " not found in " + Arrays.toString(groups), groupA); + assertNotNull(GROUP_B + " not found in " + Arrays.toString(groups), groupB); + assertNotNull(GROUP_C + " not found in " + Arrays.toString(groups), groupC); // Check group A in more detail assertEquals(GROUP_A, groupA.getShortName()); @@ -440,7 +441,7 @@ public class ScriptAuthorityServiceTest extends TestCase new ScriptPagingDetails(10, 0), "userName"); - assertEquals("Users count wrong " + users, 3, users.length); + assertEquals("Users count wrong " + Arrays.toString(users), 3, users.length); assertEquals(USER_A, users[0].getPerson().getProperties().get("userName")); assertEquals(USER_B, users[1].getPerson().getProperties().get("userName")); assertEquals(USER_C, users[2].getPerson().getProperties().get("userName")); @@ -451,7 +452,7 @@ public class ScriptAuthorityServiceTest extends TestCase new ScriptPagingDetails(10, 0), "firstName"); - assertEquals("Users count wrong " + users, 3, users.length); + assertEquals("Users count wrong " + Arrays.toString(users), 3, users.length); assertEquals(USER_B, users[0].getPerson().getProperties().get("userName")); assertEquals(USER_A, users[1].getPerson().getProperties().get("userName")); assertEquals(USER_C, users[2].getPerson().getProperties().get("userName")); @@ -462,7 +463,7 @@ public class ScriptAuthorityServiceTest extends TestCase new ScriptPagingDetails(10, 0), "lastName"); - assertEquals("Users count wrong " + users, 3, users.length); + assertEquals("Users count wrong " + Arrays.toString(users), 3, users.length); assertEquals(USER_C, users[0].getPerson().getProperties().get("userName")); assertEquals(USER_A, users[1].getPerson().getProperties().get("userName")); assertEquals(USER_B, users[2].getPerson().getProperties().get("userName")); diff --git a/source/java/org/alfresco/repo/security/permissions/impl/PublicServiceAccessServiceImpl.java b/source/java/org/alfresco/repo/security/permissions/impl/PublicServiceAccessServiceImpl.java index e5c5c878a1..69d97a428c 100644 --- a/source/java/org/alfresco/repo/security/permissions/impl/PublicServiceAccessServiceImpl.java +++ b/source/java/org/alfresco/repo/security/permissions/impl/PublicServiceAccessServiceImpl.java @@ -1,203 +1,204 @@ -/* - * 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.security.permissions.impl; - -import java.lang.reflect.Method; - -import org.alfresco.repo.security.permissions.impl.acegi.MethodSecurityInterceptor; -import org.alfresco.service.cmr.security.AccessStatus; -import org.alfresco.service.cmr.security.PublicServiceAccessService; -import org.aopalliance.intercept.MethodInvocation; -import org.springframework.aop.framework.ReflectiveMethodInvocation; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.beans.factory.ListableBeanFactory; - -public class PublicServiceAccessServiceImpl implements PublicServiceAccessService, BeanFactoryAware -{ - - private ListableBeanFactory beanFactory; - - public AccessStatus hasAccess(String publicService, String methodName, Object... args) - { - Object interceptor = beanFactory.getBean(publicService + "_security"); - if (interceptor == null) - { - throw new UnsupportedOperationException("Unknown public service security implementation " + publicService); - } - if (interceptor instanceof AlwaysProceedMethodInterceptor) - { - return AccessStatus.ALLOWED; - } - - if (interceptor instanceof MethodSecurityInterceptor) - { - MethodSecurityInterceptor msi = (MethodSecurityInterceptor) interceptor; - - MethodInvocation methodInvocation = null; - Object publicServiceImpl = beanFactory.getBean(publicService); - NEXT_METHOD: for (Method method : publicServiceImpl.getClass().getMethods()) - { - if (method.getName().equals(methodName)) - { - if (method.getParameterTypes().length == args.length) - { - // check argument types are assignable - int parameterPosition = 0; - for(Class clazz : method.getParameterTypes()) - { - if(args[parameterPosition] == null) - { - if(clazz.isPrimitive()) - { - continue NEXT_METHOD; - } - else - { - // OK, null assigns to any non-primitive type - } - } - else - { - if(clazz.isPrimitive()) - { - - if(clazz.getName().equals("boolean")) - { - if(args[parameterPosition].getClass().getName().equals("java.lang.Boolean")) - { - // OK - } - else - { - continue NEXT_METHOD; - } - } - else if(clazz.getName().equals("byte")) - { - if(args[parameterPosition].getClass().getName().equals("java.lang.Byte")) - { - // OK - } - else - { - continue NEXT_METHOD; - } - } - else if(clazz.getName().equals("char")) - { - if(args[parameterPosition].getClass().getName().equals("java.lang.Char")) - { - // OK - } - else - { - continue NEXT_METHOD; - } - } - else if(clazz.getName().equals("short")) - { - if(args[parameterPosition].getClass().getName().equals("java.lang.Short")) - { - // OK - } - else - { - continue NEXT_METHOD; - } - } - else if(clazz.getName().equals("int")) - { - if(args[parameterPosition].getClass().getName().equals("java.lang.Integer")) - { - // OK - } - else - { - continue NEXT_METHOD; - } - } - else if(clazz.getName().equals("long")) - { - if(args[parameterPosition].getClass().getName().equals("java.lang.Long")) - { - // OK - } - else - { - continue NEXT_METHOD; - } - } - else if(clazz.getName().equals("float")) - { - if(args[parameterPosition].getClass().getName().equals("java.lang.Float")) - { - // OK - } - else - { - continue NEXT_METHOD; - } - } - else if(clazz.getName().equals("double")) - { - if(args[parameterPosition].getClass().getName().equals("java.lang.Double")) - { - // OK - } - else - { - continue NEXT_METHOD; - } - } - else - { - continue NEXT_METHOD; - } - - } - else if(!(clazz.isAssignableFrom(args[parameterPosition].getClass()))) - { - continue NEXT_METHOD; - } - } - parameterPosition++; - } - methodInvocation = new ReflectiveMethodInvocation(null, null, method, args, null, null) {}; - } - } - } - - if (methodInvocation == null) - { - throw new UnsupportedOperationException("Unknown public service security implementation " + publicService + "." + methodName + " with argumsnets "+args); - } - - return msi.pre(methodInvocation); - } - throw new UnsupportedOperationException("Unknown security interceptor "+interceptor.getClass()); - } - - public void setBeanFactory(BeanFactory beanFactory) throws BeansException - { - this.beanFactory = (ListableBeanFactory) beanFactory; - } - -} +/* + * 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.security.permissions.impl; + +import java.lang.reflect.Method; +import java.util.Arrays; + +import org.alfresco.repo.security.permissions.impl.acegi.MethodSecurityInterceptor; +import org.alfresco.service.cmr.security.AccessStatus; +import org.alfresco.service.cmr.security.PublicServiceAccessService; +import org.aopalliance.intercept.MethodInvocation; +import org.springframework.aop.framework.ReflectiveMethodInvocation; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.ListableBeanFactory; + +public class PublicServiceAccessServiceImpl implements PublicServiceAccessService, BeanFactoryAware +{ + + private ListableBeanFactory beanFactory; + + public AccessStatus hasAccess(String publicService, String methodName, Object... args) + { + Object interceptor = beanFactory.getBean(publicService + "_security"); + if (interceptor == null) + { + throw new UnsupportedOperationException("Unknown public service security implementation " + publicService); + } + if (interceptor instanceof AlwaysProceedMethodInterceptor) + { + return AccessStatus.ALLOWED; + } + + if (interceptor instanceof MethodSecurityInterceptor) + { + MethodSecurityInterceptor msi = (MethodSecurityInterceptor) interceptor; + + MethodInvocation methodInvocation = null; + Object publicServiceImpl = beanFactory.getBean(publicService); + NEXT_METHOD: for (Method method : publicServiceImpl.getClass().getMethods()) + { + if (method.getName().equals(methodName)) + { + if (method.getParameterTypes().length == args.length) + { + // check argument types are assignable + int parameterPosition = 0; + for(Class clazz : method.getParameterTypes()) + { + if(args[parameterPosition] == null) + { + if(clazz.isPrimitive()) + { + continue NEXT_METHOD; + } + else + { + // OK, null assigns to any non-primitive type + } + } + else + { + if(clazz.isPrimitive()) + { + + if(clazz.getName().equals("boolean")) + { + if(args[parameterPosition].getClass().getName().equals("java.lang.Boolean")) + { + // OK + } + else + { + continue NEXT_METHOD; + } + } + else if(clazz.getName().equals("byte")) + { + if(args[parameterPosition].getClass().getName().equals("java.lang.Byte")) + { + // OK + } + else + { + continue NEXT_METHOD; + } + } + else if(clazz.getName().equals("char")) + { + if(args[parameterPosition].getClass().getName().equals("java.lang.Char")) + { + // OK + } + else + { + continue NEXT_METHOD; + } + } + else if(clazz.getName().equals("short")) + { + if(args[parameterPosition].getClass().getName().equals("java.lang.Short")) + { + // OK + } + else + { + continue NEXT_METHOD; + } + } + else if(clazz.getName().equals("int")) + { + if(args[parameterPosition].getClass().getName().equals("java.lang.Integer")) + { + // OK + } + else + { + continue NEXT_METHOD; + } + } + else if(clazz.getName().equals("long")) + { + if(args[parameterPosition].getClass().getName().equals("java.lang.Long")) + { + // OK + } + else + { + continue NEXT_METHOD; + } + } + else if(clazz.getName().equals("float")) + { + if(args[parameterPosition].getClass().getName().equals("java.lang.Float")) + { + // OK + } + else + { + continue NEXT_METHOD; + } + } + else if(clazz.getName().equals("double")) + { + if(args[parameterPosition].getClass().getName().equals("java.lang.Double")) + { + // OK + } + else + { + continue NEXT_METHOD; + } + } + else + { + continue NEXT_METHOD; + } + + } + else if(!(clazz.isAssignableFrom(args[parameterPosition].getClass()))) + { + continue NEXT_METHOD; + } + } + parameterPosition++; + } + methodInvocation = new ReflectiveMethodInvocation(null, null, method, args, null, null) {}; + } + } + } + + if (methodInvocation == null) + { + throw new UnsupportedOperationException("Unknown public service security implementation " + publicService + "." + methodName + " with arguments "+Arrays.toString(args)); + } + + return msi.pre(methodInvocation); + } + throw new UnsupportedOperationException("Unknown security interceptor "+interceptor.getClass()); + } + + public void setBeanFactory(BeanFactory beanFactory) throws BeansException + { + this.beanFactory = (ListableBeanFactory) beanFactory; + } + +} diff --git a/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java b/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java index 9c6023bde8..4ce7245a2d 100644 --- a/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java +++ b/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java @@ -1662,6 +1662,12 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per usageStatus.logMessages(logger); } + public int countPeople() + { + NodeRef peopleContainer = getPeopleContainer(); + return nodeService.countChildAssocs(peopleContainer, true); + } + /** * Helper for when creating new users and people: * Updates the supplied username with any required tenant diff --git a/source/java/org/alfresco/repo/security/sync/ldap/LDAPUserRegistry.java b/source/java/org/alfresco/repo/security/sync/ldap/LDAPUserRegistry.java index ad36cce9b3..f3cce214b4 100644 --- a/source/java/org/alfresco/repo/security/sync/ldap/LDAPUserRegistry.java +++ b/source/java/org/alfresco/repo/security/sync/ldap/LDAPUserRegistry.java @@ -906,9 +906,12 @@ public class LDAPUserRegistry implements UserRegistry, LDAPNameResolver, Initial ctx = this.ldapInitialContextFactory.getDefaultIntialDirContext(); // Execute the user query with an additional condition that ensures only the user with the required ID is - // returned + // returned. Force RFC 2254 escaping of the user ID in the filter to avoid any manipulation NamingEnumeration searchResults = ctx.search(this.userSearchBase, "(&" + this.personQuery - + "(" + this.userIdAttributeName + "=" + userId + "))", userSearchCtls); + + "(" + this.userIdAttributeName + "={0}))", new Object[] + { + userId + }, userSearchCtls); if (searchResults.hasMore()) { diff --git a/source/java/org/alfresco/repo/solr/AlfrescoModel.java b/source/java/org/alfresco/repo/solr/AlfrescoModel.java index 71ba9fa8fb..6af8c327af 100644 --- a/source/java/org/alfresco/repo/solr/AlfrescoModel.java +++ b/source/java/org/alfresco/repo/solr/AlfrescoModel.java @@ -1,55 +1,57 @@ -package org.alfresco.repo.solr; - -import org.alfresco.service.cmr.dictionary.ModelDefinition; - -/** - * Represents an alfresco model and checksum. - * - * @since 4.0 - */ -public class AlfrescoModel -{ - private ModelDefinition modelDef; - private long checksum; - - protected AlfrescoModel(ModelDefinition modelDef) - { - this.modelDef = modelDef; - this.checksum = modelDef.getChecksum(ModelDefinition.XMLBindingType.DEFAULT); - } - - public ModelDefinition getModelDef() - { - return modelDef; - } - - public long getChecksum() - { - return checksum; - } - - public boolean equals(Object other) - { - if (this == other) - { - return true; - } - - if(!(other instanceof AlfrescoModel)) - { - return false; - } - - AlfrescoModel model = (AlfrescoModel)other; - return (modelDef.getName().equals(model.getModelDef().getName()) && - checksum == model.getChecksum()); - } - - public int hashcode() - { - int result = 17; - result = 31 * result + modelDef.hashCode(); - result = 31 * result + Long.valueOf(checksum).hashCode(); - return result; - } -} +package org.alfresco.repo.solr; + +import org.alfresco.service.cmr.dictionary.ModelDefinition; + +/** + * Represents an alfresco model and checksum. + * + * @since 4.0 + */ +public class AlfrescoModel +{ + private ModelDefinition modelDef; + private long checksum; + + protected AlfrescoModel(ModelDefinition modelDef) + { + this.modelDef = modelDef; + this.checksum = modelDef.getChecksum(ModelDefinition.XMLBindingType.DEFAULT); + } + + public ModelDefinition getModelDef() + { + return modelDef; + } + + public long getChecksum() + { + return checksum; + } + + @Override + public boolean equals(Object other) + { + if (this == other) + { + return true; + } + + if(!(other instanceof AlfrescoModel)) + { + return false; + } + + AlfrescoModel model = (AlfrescoModel)other; + return (modelDef.getName().equals(model.getModelDef().getName()) && + checksum == model.getChecksum()); + } + + @Override + public int hashCode() + { + int result = 17; + result = 31 * result + modelDef.hashCode(); + result = 31 * result + Long.valueOf(checksum).hashCode(); + return result; + } +} diff --git a/source/java/org/alfresco/repo/solr/SOLRAdminClient.java b/source/java/org/alfresco/repo/solr/SOLRAdminClient.java index a6a93aa17f..8591946896 100644 --- a/source/java/org/alfresco/repo/solr/SOLRAdminClient.java +++ b/source/java/org/alfresco/repo/solr/SOLRAdminClient.java @@ -317,12 +317,25 @@ public class SOLRAdminClient implements ApplicationEventPublisherAware scheduler.start(); - JobDetail job = new JobDetail("SolrWatcher", "Solr", SOLRWatcherJob.class); + final String jobName = "SolrWatcher"; + final String jobGroup = "Solr"; + + // If a Quartz job already exists with this name and group then we want to replace it. + // It is not expected that this will occur during production, but it is possible during automated testing + // where application contexts could be rebuilt between test cases, leading to multiple creations of + // equivalent Quartz jobs. Quartz disallows the scheduling of multiple jobs with the same name and group. + JobDetail existingJob = scheduler.getJobDetail(jobName, jobGroup); + if (existingJob != null) + { + scheduler.deleteJob(jobName, jobGroup); + } + + JobDetail job = new JobDetail(jobName, jobGroup, SOLRWatcherJob.class); JobDataMap jobDataMap = new JobDataMap(); jobDataMap.put("SOLR_TRACKER", this); job.setJobDataMap(jobDataMap); - trigger = new CronTrigger("SolrWatcherTrigger", "Solr", solrPingCronExpression); + trigger = new CronTrigger("SolrWatcherTrigger", jobGroup, solrPingCronExpression); scheduler.scheduleJob(job, trigger); } catch(Exception e) diff --git a/source/java/org/alfresco/repo/tagging/TaggingServiceImpl.java b/source/java/org/alfresco/repo/tagging/TaggingServiceImpl.java index 1a3a938f13..4c5ce88851 100644 --- a/source/java/org/alfresco/repo/tagging/TaggingServiceImpl.java +++ b/source/java/org/alfresco/repo/tagging/TaggingServiceImpl.java @@ -1152,7 +1152,7 @@ public class TaggingServiceImpl implements TaggingService, // If the boolean value is different then the tag had been added and removed or // removed and then added in the same transaction. In both cases the net change is none. // So remove the entry in the update map - updates.remove(tag); + nodeDetails.remove(tag); } // Otherwise do nothing because we have already noted the update } diff --git a/source/java/org/alfresco/repo/template/TemplateNode.java b/source/java/org/alfresco/repo/template/TemplateNode.java index 4c4717e1a4..c1ef5cc4b9 100644 --- a/source/java/org/alfresco/repo/template/TemplateNode.java +++ b/source/java/org/alfresco/repo/template/TemplateNode.java @@ -53,6 +53,7 @@ import org.alfresco.service.namespace.NamespacePrefixResolverProvider; import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.QNameMap; import org.alfresco.service.namespace.RegexQNamePattern; +import org.alfresco.util.ISO9075; import org.alfresco.util.UrlUtil; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -568,7 +569,7 @@ public class TemplateNode extends BasePermissionsNode implements NamespacePrefix }; // resolve the path of the node - final String nodePath = services.getNodeService().getPath(this.nodeRef).toPrefixString(services.getNamespaceService()); + final String nodePath = ISO9075.decode(services.getNodeService().getPath(this.nodeRef).toPrefixString(services.getNamespaceService())); // run as admin user to allow everyone to see audit information // (new 3.4 API doesn't allow this by default) diff --git a/source/java/org/alfresco/repo/tenant/MultiTAdminServiceImpl.java b/source/java/org/alfresco/repo/tenant/MultiTAdminServiceImpl.java index a95b2c26dc..e899aa69e6 100644 --- a/source/java/org/alfresco/repo/tenant/MultiTAdminServiceImpl.java +++ b/source/java/org/alfresco/repo/tenant/MultiTAdminServiceImpl.java @@ -93,7 +93,6 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo private WorkflowService workflowService; private RepositoryExporterService repositoryExporterService; private ModuleService moduleService; - private SiteAVMBootstrap siteAVMBootstrap; private List workflowDeployers = new ArrayList(); private String baseAdminUsername = null; @@ -198,11 +197,6 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo this.moduleService = moduleService; } - public void setSiteAVMBootstrap(SiteAVMBootstrap siteAVMBootstrap) - { - this.siteAVMBootstrap = siteAVMBootstrap; - } - public void setBaseAdminUsername(String baseAdminUsername) { this.baseAdminUsername = baseAdminUsername; @@ -234,14 +228,6 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo logger.warn(I18NUtil.getMessage(WARN_MSG)); } - // for upgrade/backwards compatibility with 3.0.x (mt-admin-context.xml) - if (siteAVMBootstrap == null) - { - logger.warn(I18NUtil.getMessage(WARN_MSG)); - - siteAVMBootstrap = (SiteAVMBootstrap) ctx.getBean("siteAVMBootstrap"); - } - PropertyCheck.mandatory(this, "NodeService", nodeService); PropertyCheck.mandatory(this, "DictionaryComponent", dictionaryComponent); PropertyCheck.mandatory(this, "RepoAdminService", repoAdminService); @@ -253,7 +239,6 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo PropertyCheck.mandatory(this, "WorkflowService", workflowService); PropertyCheck.mandatory(this, "RepositoryExporterService", repositoryExporterService); PropertyCheck.mandatory(this, "moduleService", moduleService); - PropertyCheck.mandatory(this, "siteAVMBootstrap", siteAVMBootstrap); } public void setApplicationContext(ApplicationContext applicationContext) throws BeansException @@ -380,8 +365,6 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo ImporterBootstrap spacesImporterBootstrap = (ImporterBootstrap)ctx.getBean("spacesBootstrap-mt"); bootstrapSpacesTenantStore(spacesImporterBootstrap, tenantDomain); - siteAVMBootstrap.bootstrap(); - // notify listeners that tenant has been created & hence enabled for (TenantDeployer tenantDeployer : tenantDeployers) { diff --git a/source/java/org/alfresco/repo/tenant/MultiTDemoTest.java b/source/java/org/alfresco/repo/tenant/MultiTDemoTest.java index cbfb869b4b..6fd6b63d6a 100644 --- a/source/java/org/alfresco/repo/tenant/MultiTDemoTest.java +++ b/source/java/org/alfresco/repo/tenant/MultiTDemoTest.java @@ -131,7 +131,7 @@ public class MultiTDemoTest extends TestCase public static final String TEST_USER3 = "eve"; public static final String TEST_USER4 = "fred"; - private static final int DEFAULT_STORE_COUNT = 7; // including siteStore + private static final int DEFAULT_STORE_COUNT = 6; // Now 6 site store has been removed public static StoreRef SPACES_STORE = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore"); diff --git a/source/java/org/alfresco/repo/thumbnail/CreateThumbnailActionExecuter.java b/source/java/org/alfresco/repo/thumbnail/CreateThumbnailActionExecuter.java index 2557c90200..0260ddda97 100644 --- a/source/java/org/alfresco/repo/thumbnail/CreateThumbnailActionExecuter.java +++ b/source/java/org/alfresco/repo/thumbnail/CreateThumbnailActionExecuter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2011 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -26,11 +26,12 @@ import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.repo.action.ParameterDefinitionImpl; import org.alfresco.repo.action.executer.ActionExecuterAbstractBase; -import org.alfresco.repo.content.transform.TransformerDebug; import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ActionServiceTransientException; import org.alfresco.service.cmr.action.ParameterDefinition; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.ContentServiceTransientException; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.thumbnail.ThumbnailService; @@ -176,14 +177,26 @@ public class CreateThumbnailActionExecuter extends ActionExecuterAbstractBase { this.thumbnailService.createThumbnail(actionedUponNodeRef, contentProperty, details.getMimetype(), details.getTransformationOptions(), thumbnailName, null); } + catch (ContentServiceTransientException cste) + { + // any transient failures in the thumbnail creation must be handled as transient failures of the action to execute. + StringBuilder msg = new StringBuilder(); + msg.append("Creation of thumbnail '") .append(details.getName()) .append("' declined"); + if (logger.isDebugEnabled()) + { + logger.debug(msg.toString()); + } + + throw new ActionServiceTransientException(msg.toString(), cste); + } catch (Exception exception) { final String msg = "Creation of thumbnail '" + details.getName() + "' failed"; logger.info(msg); - // We need to rethrow in order to trigger the compensating action. - // See AddFailedThumbnailActionExecuter - throw new AlfrescoRuntimeException(msg, exception); + // We need to rethrow in order to trigger the compensating action. + // See AddFailedThumbnailActionExecuter + throw new AlfrescoRuntimeException(msg, exception); } } } diff --git a/source/java/org/alfresco/repo/thumbnail/ThumbnailServiceImplTest.java b/source/java/org/alfresco/repo/thumbnail/ThumbnailServiceImplTest.java index 85db20e12e..be2ff0c000 100644 --- a/source/java/org/alfresco/repo/thumbnail/ThumbnailServiceImplTest.java +++ b/source/java/org/alfresco/repo/thumbnail/ThumbnailServiceImplTest.java @@ -22,7 +22,7 @@ package org.alfresco.repo.thumbnail; import java.io.File; import java.io.IOException; import java.io.Serializable; -import java.util.Date; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -30,6 +30,7 @@ import java.util.Map; import org.alfresco.model.ContentModel; import org.alfresco.model.RenditionModel; import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.content.transform.AbstractContentTransformer2; import org.alfresco.repo.content.transform.AbstractContentTransformerTest; import org.alfresco.repo.content.transform.ContentTransformer; import org.alfresco.repo.content.transform.magick.ImageResizeOptions; @@ -45,17 +46,19 @@ import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.ContentIOException; import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentServiceTransientException; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.ScriptLocation; import org.alfresco.service.cmr.repository.ScriptService; +import org.alfresco.service.cmr.repository.TransformationOptions; import org.alfresco.service.cmr.thumbnail.FailedThumbnailInfo; -import org.alfresco.service.cmr.thumbnail.ThumbnailException; import org.alfresco.service.cmr.thumbnail.ThumbnailParentAssociationDetails; import org.alfresco.service.cmr.thumbnail.ThumbnailService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.RegexQNamePattern; +import org.alfresco.util.ApplicationContextHelper; import org.alfresco.util.BaseAlfrescoSpringTest; import org.alfresco.util.TempFileProvider; @@ -75,6 +78,7 @@ public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest private RetryingTransactionHelper transactionHelper; private ServiceRegistry services; private NodeRef folder; + private static final String TEST_FAILING_MIME_TYPE = "application/vnd.alfresco.test.transientfailure"; /** * Called during the transaction setup @@ -101,7 +105,19 @@ public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "testFolder"), ContentModel.TYPE_FOLDER) .getChildRef(); } - + + @Override protected String[] getConfigLocations() + { + List configLocations = new ArrayList(); + for (String config : ApplicationContextHelper.CONFIG_LOCATIONS) + { + configLocations.add(config); + } + configLocations.add("classpath*:org.alfresco.repo.thumbnail.test-thumbnail-context.xml"); + + return configLocations.toArray(new String[0]); + } + private void checkTransformer() { ContentTransformer transformer = this.contentService.getImageTransformer(); @@ -348,8 +364,66 @@ public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest } }); } + + /** + * From 4.0.1 we support 'transient' thumbnail failure. This occurs when the {@link ContentTransformer} + * cannot attempt to perform the transformation for some reason (e.g. process/service unavailable) and wishes + * to decline the request. Such 'failures' should not lead to the addition of the {@link ContentModel#ASPECT_FAILED_THUMBNAIL_SOURCE} + * aspect. + * + * @since 4.0.1 + */ + public void testCreateTransientlyFailingThumbnail() throws Exception + { + Map props = new HashMap(); + props.put(ContentModel.PROP_NAME, "transientThumbnail.transientThumbnail"); + final NodeRef testNode = this.nodeService.createNode(folder, ContentModel.ASSOC_CONTAINS, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "transientThumbnail.transientThumbnail"), + ContentModel.TYPE_CONTENT, props).getChildRef(); + + nodeService.setProperty(testNode, ContentModel.PROP_CONTENT, + new ContentData(null, TEST_FAILING_MIME_TYPE, 0L, null)); + // We don't need to write any content into this node, as our test transformer will fail immediately. + logger.debug("Running failing thumbnail on " + testNode); + + // Make sure the source node is correctly set up before we start + // It should not be renditioned and should not be marked as having any failed thumbnails. + assertFalse(nodeService.hasAspect(testNode, RenditionModel.ASPECT_RENDITIONED)); + assertFalse(nodeService.hasAspect(testNode, ContentModel.ASPECT_FAILED_THUMBNAIL_SOURCE)); + + setComplete(); + endTransaction(); + // Attempt to perform a thumbnail that we know will fail. + transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + ThumbnailDefinition thumbnailDef = thumbnailService.getThumbnailRegistry().getThumbnailDefinition("doclib"); + + Action createThumbnailAction = ThumbnailHelper.createCreateThumbnailAction(thumbnailDef, services); + actionService.executeAction(createThumbnailAction, testNode, true, true); + return null; + } + }); + // The thumbnail attempt has now failed. But in this case the compensating action should NOT have been scheduled. + // We'll wait briefly in case it has erroneously been scheduled. + + Thread.sleep(3000); // This should be long enough for the compensating action to run - if it has been scheduled, which it shouldn't. + + transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + assertFalse("Node should not have renditioned aspect", nodeService.hasAspect(testNode, RenditionModel.ASPECT_RENDITIONED)); + assertFalse("Node should not have failed thumbnails aspect", nodeService.hasAspect(testNode, ContentModel.ASPECT_FAILED_THUMBNAIL_SOURCE)); + + return null; + } + }); + } + public void testThumbnailUpdate() throws Exception { checkTransformer(); @@ -708,4 +782,23 @@ public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest final String standardMediumIcon = "alfresco/thumbnail/thumbnail_placeholder_medium.jpg"; // This one jpg, not png assertEquals(standardMediumIcon, mediumIcon); } + + /** + * Test transformer. + * + * @since 4.0.1 + */ + private static class TransientFailTransformer extends AbstractContentTransformer2 + { + public boolean isTransformable(String sourceMimetype, String targetMimetype, TransformationOptions options) + { + return sourceMimetype.equals(MimetypeMap.MIMETYPE_PDF) && targetMimetype.equals(TEST_FAILING_MIME_TYPE); + } + + protected void transformInternal(ContentReader reader, ContentWriter writer, TransformationOptions options) throws Exception + { + // fail every time. + throw new ContentServiceTransientException("Transformation intentionally failed for test purposes."); + } + } } diff --git a/source/java/org/alfresco/repo/thumbnail/test-thumbnail-context.xml b/source/java/org/alfresco/repo/thumbnail/test-thumbnail-context.xml new file mode 100644 index 0000000000..1c86c80c5d --- /dev/null +++ b/source/java/org/alfresco/repo/thumbnail/test-thumbnail-context.xml @@ -0,0 +1,17 @@ + + + + + + + + + application/vnd.alfresco.test.transientfailure + image/png + + + + + diff --git a/source/java/org/alfresco/repo/transaction/TransactionAwareSingletonTest.java b/source/java/org/alfresco/repo/transaction/TransactionAwareSingletonTest.java index 33c780be57..02d6bf89c0 100644 --- a/source/java/org/alfresco/repo/transaction/TransactionAwareSingletonTest.java +++ b/source/java/org/alfresco/repo/transaction/TransactionAwareSingletonTest.java @@ -211,7 +211,14 @@ public class TransactionAwareSingletonTest extends TestCase public Object execute() throws Exception { Integer actual = singleton.get(); - assertTrue("Values don't match: " + expected + " != " + actual, actual == expected); + if (expected == null) + { + assertNull("Expected null but got " + actual, actual); + } + else + { + assertTrue("Values don't match: " + expected + " != " + actual, actual.equals(expected)); + } return null; } }; diff --git a/source/java/org/alfresco/repo/transfer/RepoPrimaryManifestProcessorImpl.java b/source/java/org/alfresco/repo/transfer/RepoPrimaryManifestProcessorImpl.java index db1a5204da..ac15fd08c5 100644 --- a/source/java/org/alfresco/repo/transfer/RepoPrimaryManifestProcessorImpl.java +++ b/source/java/org/alfresco/repo/transfer/RepoPrimaryManifestProcessorImpl.java @@ -452,6 +452,12 @@ public class RepoPrimaryManifestProcessorImpl extends AbstractManifestProcessorB } } } + else + { + logComment("Not updating local node - node is local to this repository): " + node.getNodeRef()); + // Not a transferred node. + return; + } QName parentAssocType = primaryParentAssoc.getTypeQName(); QName parentAssocName = primaryParentAssoc.getQName(); diff --git a/source/java/org/alfresco/repo/transfer/RepoTransferReceiverImpl.java b/source/java/org/alfresco/repo/transfer/RepoTransferReceiverImpl.java index 4682865498..82bd6f2e16 100644 --- a/source/java/org/alfresco/repo/transfer/RepoTransferReceiverImpl.java +++ b/source/java/org/alfresco/repo/transfer/RepoTransferReceiverImpl.java @@ -619,7 +619,7 @@ public class RepoTransferReceiverImpl implements TransferReceiver, { log.debug("releasing lock:" + lock.lockToken); lock.releaseLock(); - locks.remove(lock); + locks.remove(transferId); } removeTempFolders(transferId); diff --git a/source/java/org/alfresco/repo/transfer/TransferServiceImplTest.java b/source/java/org/alfresco/repo/transfer/TransferServiceImplTest.java index 0c36e9a7e0..708a4625cf 100644 --- a/source/java/org/alfresco/repo/transfer/TransferServiceImplTest.java +++ b/source/java/org/alfresco/repo/transfer/TransferServiceImplTest.java @@ -417,6 +417,7 @@ public class TransferServiceImplTest extends BaseAlfrescoSpringTest */ transferService.createAndSaveTransferTarget(getMe, title, description, endpointProtocol, endpointHost, endpointPort, endpointPath, username, password); + //TODO MER Test assumes an English Locale. (Fails with French) Set targets = transferService.getTransferTargets("Default Group"); assertTrue("targets is empty", targets.size() > 0); /** @@ -3280,6 +3281,180 @@ public class TransferServiceImplTest extends BaseAlfrescoSpringTest } // testPeerAssocs + + /** + * Test Existing nodes. ALF-12262 + * + * Guest Home + * | + * GUID + * | + * A1 B1 + * | | + * A2 B2 + * + * @throws Exception + */ + public void testExistingNodes() throws Exception + { + final RetryingTransactionHelper tran = transactionService.getRetryingTransactionHelper(); + final String CONTENT_TITLE = "ContentTitle"; + final Locale CONTENT_LOCALE = Locale.GERMAN; + final String CONTENT_ENCODING = "UTF-8"; + + /** + * For unit test + * - replace the HTTP transport with the in-process transport + * - replace the node factory with one that will map node refs, paths etc. + * + * Fake Repository Id + */ + final TransferTransmitter transmitter = new UnitTestInProcessTransmitterImpl(receiver, contentService, transactionService); + transferServiceImpl.setTransmitter(transmitter); + final UnitTestTransferManifestNodeFactory testNodeFactory = new UnitTestTransferManifestNodeFactory(this.transferManifestNodeFactory); + transferServiceImpl.setTransferManifestNodeFactory(testNodeFactory); + final List> pathMap = testNodeFactory.getPathMap(); + + // Map company_home/guest_home to company_home so tranferred nodes and moved "up" one level. + pathMap.add(new Pair(PathHelper.stringToPath(GUEST_HOME_XPATH_QUERY), PathHelper.stringToPath(COMPANY_HOME_XPATH_QUERY))); + + final String targetName = "testExistingNodes"; + + class TestContext + { + TransferTarget transferMe; + NodeRef folderNodeRef; + NodeRef contentNodeRef; + NodeRef rootNodeRef; + NodeRef destFolderNodeRef; + NodeRef destFileNodeRef; + }; + + RetryingTransactionCallback setupCB = new RetryingTransactionCallback() + { + @Override + public TestContext execute() throws Throwable + { + TestContext testContext = new TestContext(); + + /** + * Get guest home + */ + String guestHomeQuery = "/app:company_home/app:guest_home"; + ResultSet guestHomeResult = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, SearchService.LANGUAGE_XPATH, guestHomeQuery); + assertEquals("", 1, guestHomeResult.length()); + NodeRef guestHome = guestHomeResult.getNodeRef(0); + guestHomeResult.close(); + + String companyHomeQuery = "/app:company_home"; + ResultSet companyHomeResult = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, SearchService.LANGUAGE_XPATH, companyHomeQuery); + assertEquals("", 1, companyHomeResult.length()); + NodeRef companyHome = companyHomeResult.getNodeRef(0); + companyHomeResult.close(); + + + /** + * Create a test node that we will read and write + */ + String name = GUID.generate(); + + TransferDefinition def = new TransferDefinition(); + + ChildAssociationRef child = nodeService.createNode(guestHome, ContentModel.ASSOC_CONTAINS, QName.createQName(name), ContentModel.TYPE_FOLDER); + testContext.rootNodeRef = child.getChildRef(); + nodeService.setProperty(testContext.rootNodeRef, ContentModel.PROP_TITLE, name); + nodeService.setProperty(testContext.rootNodeRef, ContentModel.PROP_NAME, name); + + // Side effect - initialisee nodeid mapping + testNodeFactory.createTransferManifestNode(testContext.rootNodeRef, def); + + child = nodeService.createNode(testContext.rootNodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A1"), ContentModel.TYPE_FOLDER); + testContext.folderNodeRef = child.getChildRef(); + nodeService.setProperty(testContext.folderNodeRef, ContentModel.PROP_TITLE, "A1"); + nodeService.setProperty(testContext.folderNodeRef, ContentModel.PROP_NAME, "A1"); + + // Side effect - initialise nodeid mapping + testNodeFactory.createTransferManifestNode(testContext.folderNodeRef, def); + + child = nodeService.createNode(testContext.folderNodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A2"), ContentModel.TYPE_CONTENT); + testContext.contentNodeRef = child.getChildRef(); + nodeService.setProperty(testContext.contentNodeRef, ContentModel.PROP_TITLE, "A2"); + nodeService.setProperty(testContext.contentNodeRef, ContentModel.PROP_NAME, "A2"); + + // Side effect - initialise nodeid mapping + testNodeFactory.createTransferManifestNode(testContext.contentNodeRef, def); + + + // Put nodes into destination + child = nodeService.createNode(companyHome, ContentModel.ASSOC_CONTAINS, QName.createQName(name), ContentModel.TYPE_FOLDER); + //testContext.rootNodeRef = child.getChildRef(); + nodeService.setProperty(testContext.rootNodeRef, ContentModel.PROP_TITLE, name); + nodeService.setProperty(testContext.rootNodeRef, ContentModel.PROP_NAME, name); + + child = nodeService.createNode(child.getChildRef(), ContentModel.ASSOC_CONTAINS, QName.createQName("A1"), ContentModel.TYPE_FOLDER); + testContext.destFolderNodeRef = child.getChildRef(); + nodeService.setProperty(testContext.destFolderNodeRef, ContentModel.PROP_TITLE, "A1"); + nodeService.setProperty(testContext.destFolderNodeRef, ContentModel.PROP_NAME, "A1"); + + child = nodeService.createNode(testContext.destFolderNodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A2"), ContentModel.TYPE_CONTENT); + testContext.destFileNodeRef = child.getChildRef(); + nodeService.setProperty(testContext.destFileNodeRef, ContentModel.PROP_TITLE, "A2"); + nodeService.setProperty(testContext.destFileNodeRef, ContentModel.PROP_NAME, "A2"); + + /** + * Make sure the transfer target exists and is enabled. + */ + if(!transferService.targetExists(targetName)) + { + testContext.transferMe = createTransferTarget(targetName); + } + else + { + testContext.transferMe = transferService.getTransferTarget(targetName); + } + transferService.enableTransferTarget(targetName, true); + return testContext; + } + }; + + final TestContext testContext = tran.doInTransaction(setupCB); + + RetryingTransactionCallback transferCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + TransferDefinition definition = new TransferDefinition(); + Setnodes = new HashSet(); + nodes.add(testContext.rootNodeRef); + nodes.add(testContext.folderNodeRef); + nodes.add(testContext.contentNodeRef); + definition.setNodes(nodes); + transferService.transfer(targetName, definition); + return null; + } + }; + + RetryingTransactionCallback validateCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + assertTrue("folder has transferred aspect", !nodeService.hasAspect(testContext.destFolderNodeRef, TransferModel.ASPECT_TRANSFERRED)); + assertTrue("file has transferred aspctet", !nodeService.hasAspect(testContext.destFileNodeRef, TransferModel.ASPECT_TRANSFERRED)); + return null; + } + }; + + /** + * This is the test + */ + + tran.doInTransaction(transferCB); + tran.doInTransaction(validateCB); + + } // testExistingNodes + // Utility methods below. private TransferTarget createTransferTarget(String name) { diff --git a/source/java/org/alfresco/repo/version/NodeServiceImpl.java b/source/java/org/alfresco/repo/version/NodeServiceImpl.java index adca764c36..804f358094 100644 --- a/source/java/org/alfresco/repo/version/NodeServiceImpl.java +++ b/source/java/org/alfresco/repo/version/NodeServiceImpl.java @@ -767,4 +767,11 @@ public class NodeServiceImpl implements NodeService, VersionModel // This operation is not supported for a version store throw new UnsupportedOperationException(MSG_UNSUPPORTED); } + + @Override + public int countChildAssocs(NodeRef nodeRef, boolean isPrimary) throws InvalidNodeRefException + { + // This operation is not supported for a version store + throw new UnsupportedOperationException(MSG_UNSUPPORTED); + } } diff --git a/source/java/org/alfresco/repo/workflow/CancelWorkflowActionExecuter.java b/source/java/org/alfresco/repo/workflow/CancelWorkflowActionExecuter.java new file mode 100644 index 0000000000..a130eea541 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/CancelWorkflowActionExecuter.java @@ -0,0 +1,77 @@ +/* + * 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.workflow; + +import java.util.List; + +import org.alfresco.repo.action.ParameterDefinitionImpl; +import org.alfresco.repo.action.executer.ActionExecuterAbstractBase; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ParameterDefinition; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.workflow.WorkflowService; + +/** + * @author dward + */ +public class CancelWorkflowActionExecuter extends ActionExecuterAbstractBase +{ + public static String NAME = "cancel-workflow"; + + public static final String PARAM_WORKFLOW_ID_LIST = "workflow-id-list"; // list of workflow IDs + + private WorkflowService workflowService; + + /** + * @param workflowService the workflowService to set + */ + public void setWorkflowService(WorkflowService workflowService) + { + this.workflowService = workflowService; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.action.executer.ActionExecuterAbstractBase#executeImpl(org.alfresco.service.cmr.action.Action, org.alfresco.service.cmr.repository.NodeRef) + */ + @Override + protected void executeImpl(Action action, NodeRef actionedUponNodeRef) + { + @SuppressWarnings("unchecked") + List workflowIds = (List) action.getParameterValue(PARAM_WORKFLOW_ID_LIST); + if (workflowIds != null && !workflowIds.isEmpty()) + { + this.workflowService.cancelWorkflows(workflowIds); + } + } + + /* (non-Javadoc) + * @see org.alfresco.repo.action.ParameterizedItemAbstractBase#addParameterDefinitions(java.util.List) + */ + @Override + protected void addParameterDefinitions(List paramList) + { + paramList.add( + new ParameterDefinitionImpl(PARAM_WORKFLOW_ID_LIST, + DataTypeDefinition.ANY, + false, + getParamDisplayLabel(PARAM_WORKFLOW_ID_LIST))); + } + +} diff --git a/source/java/org/alfresco/repo/workflow/TaskComponent.java b/source/java/org/alfresco/repo/workflow/TaskComponent.java index bc0ffdda8b..d4355ab6fe 100644 --- a/source/java/org/alfresco/repo/workflow/TaskComponent.java +++ b/source/java/org/alfresco/repo/workflow/TaskComponent.java @@ -62,14 +62,23 @@ public interface TaskComponent */ public List getPooledTasks(List authorities); + /** + * @deprecated Use overloaded method with the {@code sameSession} parameter + * (this method defaults the parameter to {@code false}). + */ + public List queryTasks(WorkflowTaskQuery query); + /** * Query for tasks * * @param query the filter by which tasks are queried + * @param sameSession indicates that the returned {@link WorkflowTask} elements will be used in + * the same session. If {@code true}, the returned List will be a lazy loaded list + * providing greater performance. * @return the list of tasks matching the specified query */ - public List queryTasks(WorkflowTaskQuery query); - + public List queryTasks(final WorkflowTaskQuery query, boolean sameSession); + /** * Update the Properties and Associations of a Task * @@ -115,5 +124,13 @@ public interface TaskComponent * @return the list of active timers */ public WorkflowTask getStartTask(String workflowInstanceId); + + + /** + * Gets all start tasks for the specified workflow + * + * @return the list of start tasks + */ + public List getStartTasks(final List workflowInstanceIds, final boolean sameSession); } diff --git a/source/java/org/alfresco/repo/workflow/WorkflowComponent.java b/source/java/org/alfresco/repo/workflow/WorkflowComponent.java index 76c409099b..af7bd7764f 100644 --- a/source/java/org/alfresco/repo/workflow/WorkflowComponent.java +++ b/source/java/org/alfresco/repo/workflow/WorkflowComponent.java @@ -241,6 +241,14 @@ public interface WorkflowComponent */ public WorkflowInstance cancelWorkflow(String workflowId); + /** + * Cancel a batch of "in-flight" Workflow instances + * + * @param workflowIds List of the workflow instances to cancel + * @return List of updated representations of the workflow instances + */ + public List cancelWorkflows(List workflowIds); + /** * Delete an "in-flight" Workflow instance * diff --git a/source/java/org/alfresco/repo/workflow/WorkflowInterpreter.java b/source/java/org/alfresco/repo/workflow/WorkflowInterpreter.java index b1dd512e28..b7bf480f0d 100644 --- a/source/java/org/alfresco/repo/workflow/WorkflowInterpreter.java +++ b/source/java/org/alfresco/repo/workflow/WorkflowInterpreter.java @@ -1,1360 +1,1360 @@ -/* - * 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.workflow; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.PrintStream; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.admin.BaseInterpreter; -import org.alfresco.repo.avm.AVMNodeConverter; -import org.alfresco.repo.content.MimetypeMap; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.security.authority.AuthorityDAO; -import org.alfresco.repo.tenant.TenantService; -import org.alfresco.service.cmr.avm.AVMNodeDescriptor; -import org.alfresco.service.cmr.avm.AVMService; -import org.alfresco.service.cmr.avmsync.AVMDifference; -import org.alfresco.service.cmr.avmsync.AVMSyncService; -import org.alfresco.service.cmr.dictionary.AspectDefinition; -import org.alfresco.service.cmr.dictionary.ClassDefinition; -import org.alfresco.service.cmr.dictionary.DictionaryService; -import org.alfresco.service.cmr.dictionary.PropertyDefinition; -import org.alfresco.service.cmr.dictionary.TypeDefinition; -import org.alfresco.service.cmr.model.FileFolderService; -import org.alfresco.service.cmr.model.FileInfo; -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.datatype.DefaultTypeConverter; -import org.alfresco.service.cmr.security.PersonService; -import org.alfresco.service.cmr.workflow.WorkflowDefinition; -import org.alfresco.service.cmr.workflow.WorkflowDeployment; -import org.alfresco.service.cmr.workflow.WorkflowException; -import org.alfresco.service.cmr.workflow.WorkflowInstance; -import org.alfresco.service.cmr.workflow.WorkflowPath; -import org.alfresco.service.cmr.workflow.WorkflowService; -import org.alfresco.service.cmr.workflow.WorkflowTask; -import org.alfresco.service.cmr.workflow.WorkflowTaskQuery; -import org.alfresco.service.cmr.workflow.WorkflowTaskState; -import org.alfresco.service.cmr.workflow.WorkflowTimer; -import org.alfresco.service.cmr.workflow.WorkflowTransition; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; -import org.alfresco.service.transaction.TransactionService; -import org.alfresco.util.GUID; -import org.springframework.context.ApplicationEvent; -import org.springframework.core.io.ClassPathResource; -import org.springframework.extensions.surf.util.I18NUtil; - -/** - * An interactive console for Workflows. - * - * @author davidc - */ -public class WorkflowInterpreter extends BaseInterpreter -{ - // Service dependencies - private WorkflowService workflowService; - private NamespaceService namespaceService; - private NodeService nodeService; - private AuthorityDAO authorityDAO; - private AVMService avmService; - private AVMSyncService avmSyncService; - private PersonService personService; - private FileFolderService fileFolderService; - private TenantService tenantService; - private DictionaryService dictionaryService; - - /** - * Current context - */ - private WorkflowDefinition currentWorkflowDef = null; - private WorkflowPath currentPath = null; - private String currentDeployResource = null; - private String currentDeployEngine = null; - - - /** - * Variables - */ - private Map vars = new HashMap(); - - - - - /* (non-Javadoc) - * @see org.springframework.extensions.surf.util.AbstractLifecycleBean#onBootstrap(org.springframework.getContext().ApplicationEvent) - */ - @Override - protected void onBootstrap(ApplicationEvent event) - { - //NOOP - } - - /* (non-Javadoc) - * @see org.springframework.extensions.surf.util.AbstractLifecycleBean#onShutdown(org.springframework.getContext().ApplicationEvent) - */ - @Override - protected void onShutdown(ApplicationEvent event) - { - // NOOP - } - - /** - * @param workflowService The Workflow Service - */ - public void setWorkflowService(WorkflowService workflowService) - { - this.workflowService = workflowService; - } - - /** - * @param nodeService The Node Service - */ - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - /** - * @param tenantService The Tenant Service - */ - public void setTenantService(TenantService tenantService) - { - this.tenantService = tenantService; - } - - /** - * @param dictionaryService dictionaryService - */ - public void setDictionaryService(DictionaryService dictionaryService) - { - this.dictionaryService = dictionaryService; - } - - /** - * @param avmService The AVM Service - */ - public void setAVMService(AVMService avmService) - { - this.avmService = avmService; - } - - /** - * @param avmSyncService The AVM Sync Service - */ - public void setAVMSyncService(AVMSyncService avmSyncService) - { - this.avmSyncService = avmSyncService; - } - - /** - * @param namespaceService namespaceService - */ - public void setNamespaceService(NamespaceService namespaceService) - { - this.namespaceService = namespaceService; - } - - /** - * @param personService personService - */ - public void setPersonService(PersonService personService) - { - this.personService = personService; - } - - /** - * @param transactionService transactionService - */ - @Override - public void setTransactionService(TransactionService transactionService) - { - this.transactionService = transactionService; - } - - /** - * @param authorityDAO authorityDAO - */ - public void setAuthorityDAO(AuthorityDAO authorityDAO) - { - this.authorityDAO = authorityDAO; - } - - /** - * @param fileFolderService fileFolderService - */ - public void setFileFolderService(FileFolderService fileFolderService) - { - this.fileFolderService = fileFolderService; - } - - /** - * Main entry point. - */ - public static void main(String[] args) - { - runMain("workflowInterpreter"); - } - - @Override - protected boolean hasAuthority(String username) - { - // admin can change to any user (via worklow command "user ") - return true; - } - - /** - * Execute a single command using the BufferedReader passed in for any data needed. - * - * TODO: Use decent parser! - * - * @param line The unparsed command - * @return The textual output of the command. - */ - @Override - protected String executeCommand(String line) throws IOException - { - String[] command = line.split(" "); - if (command.length == 0) - { - command = new String[1]; - command[0] = line; - } - - ByteArrayOutputStream bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - - // repeat last command? - if (command[0].equals("r")) - { - if (lastCommand == null) - { - return "No command entered yet."; - } - return "repeating command " + lastCommand + "\n\n" + executeCommand(lastCommand); - } - - // remember last command - lastCommand = line; - - // execute command - if (command[0].equals("help")) - { - String helpFile = I18NUtil.getMessage("workflow_console.help"); - ClassPathResource helpResource = new ClassPathResource(helpFile); - byte[] helpBytes = new byte[500]; - InputStream helpStream = helpResource.getInputStream(); - try - { - int read = helpStream.read(helpBytes); - while (read != -1) - { - bout.write(helpBytes, 0, read); - read = helpStream.read(helpBytes); - } - } - finally - { - helpStream.close(); - } - } - - else if (command[0].equals("show")) - { - if (command.length < 2) - { - return "Syntax Error.\n"; - } - - else if (command[1].equals("file")) - { - if (command.length != 3) - { - return "Syntax Error.\n"; - } - ClassPathResource file = new ClassPathResource(command[2]); - InputStream fileStream = file.getInputStream(); - byte[] fileBytes = new byte[500]; - try - { - int read = fileStream.read(fileBytes); - while (read != -1) - { - bout.write(fileBytes, 0, read); - read = fileStream.read(fileBytes); - } - } - finally - { - fileStream.close(); - } - out.println(); - } - - else if (command[1].equals("definitions")) - { - List defs = null; - if (command.length == 3) - { - if (command[2].equals("all")) - { - defs = workflowService.getAllDefinitions(); - } - else - { - return "Syntax Error.\n"; - } - } - else - { - defs = workflowService.getDefinitions(); - } - for (WorkflowDefinition def : defs) - { - out.println("id: " + def.getId() + " , name: " + def.getName() + " , title: " + def.getTitle() + " , version: " + def.getVersion()); - } - } - - else if (command[1].equals("workflows")) - { - String id = (currentWorkflowDef != null) ? currentWorkflowDef.getId() : null; - if (id == null && command.length == 2) - { - return "workflow definition not in use. Enter command 'show workflows all' or 'use '.\n"; - } - if (command.length == 3) - { - if (command[2].equals("all")) - { - id = "all"; - } - else - { - return "Syntax Error.\n"; - } - } - - if ("all".equals(id)) - { - for (WorkflowDefinition def : workflowService.getAllDefinitions()) - { - List workflows = workflowService.getActiveWorkflows(def.getId()); - for (WorkflowInstance workflow : workflows) - { - out.println("id: " + workflow.getId() + " , desc: " + workflow.getDescription() + " , start date: " + workflow.getStartDate() + " , def: " + workflow.getDefinition().getName() + " v" + workflow.getDefinition().getVersion()); - } - } - } - else - { - List workflows = workflowService.getActiveWorkflows(id); - for (WorkflowInstance workflow : workflows) - { - out.println("id: " + workflow.getId() + " , desc: " + workflow.getDescription() + " , start date: " + workflow.getStartDate() + " , def: " + workflow.getDefinition().getName()); - } - } - } - - else if (command[1].equals("paths")) - { - String workflowId = (command.length == 3) ? command[2] : (currentPath == null) ? null : currentPath.getInstance().getId(); - if (workflowId == null) - { - return "Syntax Error. Workflow Id not specified.\n"; - } - List paths = workflowService.getWorkflowPaths(workflowId); - for (WorkflowPath path : paths) - { - out.println("path id: " + path.getId() + " , node: " + path.getNode().getName()); - } - } - - else if (command[1].equals("tasks")) - { - String pathId = (command.length == 3) ? command[2] : (currentPath == null) ? null : currentPath.getId(); - if (pathId == null) - { - return "Syntax Error. Path Id not specified.\n"; - } - List tasks = workflowService.getTasksForWorkflowPath(pathId); - for (WorkflowTask task : tasks) - { - out.println("task id: " + task.getId() + " , name: " + task.getName() + " , properties: " + task.getProperties().size()); - } - } - - else if (command[1].equals("transitions")) - { - String workflowId = (command.length == 3) ? command[2] : (currentPath == null) ? null : currentPath.getInstance().getId(); - if (workflowId == null) - { - return "Syntax Error. Workflow Id not specified.\n"; - } - List paths = workflowService.getWorkflowPaths(workflowId); - if (paths.size() == 0) - { - out.println("no further transitions"); - } - for (WorkflowPath path : paths) - { - out.println("path: " + path.getId() + " , node: " + path.getNode().getName() + " , active: " + path.isActive()); - List tasks = workflowService.getTasksForWorkflowPath(path.getId()); - for (WorkflowTask task : tasks) - { - out.println(" task id: " + task.getId() + " , name: " + task.getName() + ", title: " + task.getTitle() + " , desc: " + task.getDescription() + " , properties: " + task.getProperties().size()); - } - for (WorkflowTransition transition : path.getNode().getTransitions()) - { - out.println(" transition id: " + ((transition.getId() == null || transition.getId().equals("")) ? "[default]" : transition.getId()) + " , title: " + transition.getTitle()); - } - } - } - - else if (command[1].equals("timers")) - { - String id = (currentWorkflowDef != null) ? currentWorkflowDef.getId() : null; - if (id == null && command.length == 2) - { - return "workflow definition not in use. Enter command 'show timers all' or 'use '.\n"; - } - if (command.length == 3) - { - if (command[2].equals("all")) - { - id = "all"; - } - else - { - return "Syntax Error.\n"; - } - } - - List timers = new ArrayList(); - - if ("all".equals(id)) - { - for (WorkflowDefinition def : workflowService.getAllDefinitions()) - { - List workflows = workflowService.getActiveWorkflows(def.getId()); - for (WorkflowInstance workflow : workflows) - { - timers.addAll(workflowService.getTimers(workflow.getId())); - } - } - } - else - { - List workflows = workflowService.getActiveWorkflows(id); - for (WorkflowInstance workflow : workflows) - { - timers.addAll(workflowService.getTimers(workflow.getId())); - } - } - - for (WorkflowTimer timer : timers) - { - out.print("id: " + timer.getId() + " , name: " + timer.getName() + " , due date: " + timer.getDueDate() + " , path: " + timer.getPath().getId() + " , node: " + timer.getPath().getNode().getName() + " , process: " + timer.getPath().getInstance().getId()); - if (timer.getTask() != null) - { - out.print(" , task: " + timer.getTask().getName() + "(" + timer.getTask().getId() + ")"); - } - out.println(); - if (timer.getError() != null) - { - out.println("error executing timer id " + timer.getId()); - out.println(timer.getError()); - } - } - } - - else if (command[1].equals("my")) - { - if (command.length != 3) - { - return "Syntax Error.\n"; - } - - if (command[2].equals("tasks")) - { - out.println(AuthenticationUtil.getRunAsUser() + ":"); - List tasks = workflowService.getAssignedTasks(AuthenticationUtil.getRunAsUser(), WorkflowTaskState.IN_PROGRESS); - for (WorkflowTask task : tasks) - { - out.println("id: " + task.getId() + " , name: " + task.getName() + " , properties: " + task.getProperties().size() + " , workflow: " + task.getPath().getInstance().getId() + " , path: " + task.getPath().getId()); - } - } - - else if (command[2].equals("completed")) - { - out.println(AuthenticationUtil.getRunAsUser() + ":"); - List tasks = workflowService.getAssignedTasks(AuthenticationUtil.getRunAsUser(), WorkflowTaskState.COMPLETED); - for (WorkflowTask task : tasks) - { - out.println("id: " + task.getId() + " , name " + task.getName() + " , properties: " + task.getProperties().size() + " , workflow: " + task.getPath().getInstance().getId() + " , path: " + task.getPath().getId()); - } - } - - else if (command[2].equals("pooled")) - { - out.println(AuthenticationUtil.getRunAsUser() + ":"); - List tasks = workflowService.getPooledTasks(AuthenticationUtil.getRunAsUser()); - for (WorkflowTask task : tasks) - { - out.println("id: " + task.getId() + " , name " + task.getName() + " , properties: " + task.getProperties().size() + " , workflow: " + task.getPath().getInstance().getId() + " , path: " + task.getPath().getId()); - } - } - - else - { - return "Syntax Error.\n"; - } - } - else - { - return "Syntax Error.\n"; - } - } - - else if (command[0].equals("desc")) - { - if (command.length < 2) - { - return "Syntax Error.\n"; - } - - if (command[1].equals("task")) - { - if (command.length != 3) - { - return "Syntax Error.\n"; - } - WorkflowTask task = workflowService.getTaskById(command[2]); - out.println("id: " + task.getId()); - out.println("name: " + task.getName()); - out.println("title: " + task.getTitle()); - out.println("description: " + task.getDescription()); - out.println("state: " + task.getState()); - out.println("path: " + task.getPath().getId()); - out.println("transitions: " + task.getDefinition().getNode().getTransitions().length); - for (WorkflowTransition transition : task.getDefinition().getNode().getTransitions()) - { - out.println(" transition: " + ((transition.getId() == null || transition.getId().equals("")) ? "[default]" : transition.getId()) + " , title: " + transition.getTitle() + " , desc: " + transition.getDescription()); - } - out.println("properties: " + task.getProperties().size()); - for (Map.Entry prop : task.getProperties().entrySet()) - { - out.println(" " + prop.getKey() + " = " + prop.getValue()); - } - } - - else if (command[1].equals("workflow")) - { - if (command.length != 3) - { - return "Syntax Error.\n"; - } - WorkflowInstance workflow = workflowService.getWorkflowById(command[2]); - out.println("definition: " + workflow.getDefinition().getName()); - out.println("id: " + workflow.getId()); - out.println("description: " + workflow.getDescription()); - out.println("active: " + workflow.isActive()); - out.println("start date: " + workflow.getStartDate()); - out.println("end date: " + workflow.getEndDate()); - out.println("initiator: " + workflow.getInitiator()); - out.println("context: " + workflow.getContext()); - out.println("package: " + workflow.getWorkflowPackage()); - } - - else if (command[1].equals("path")) - { - if (command.length != 3) - { - return "Syntax Error.\n"; - } - Map properties = workflowService.getPathProperties(command[2]); - out.println("path: " + command[1]); - out.println("properties: " + properties.size()); - for (Map.Entry prop : properties.entrySet()) - { - out.println(" " + prop.getKey() + " = " + prop.getValue()); - } - } - - else - { - return "Syntax Error.\n"; - } - } - - else if (command[0].equals("query")) - { - if (command.length < 2) - { - return "Syntax Error.\n"; - } - - if (command[1].equals("task")) - { - // build query - WorkflowTaskQuery query = new WorkflowTaskQuery(); - Map taskProps = new HashMap(); - Map procProps = new HashMap(); - - for (int i = 2; i < command.length; i++) - { - String[] predicate = command[i].split("="); - if (predicate.length == 1) - { - return "Syntax Error.\n"; - } - String[] predicateName = predicate[0].split("\\."); - if (predicateName.length == 1) - { - if (predicate[0].equals("taskId")) - { - query.setTaskId(predicate[1]); - } - else if (predicate[0].equals("taskState")) - { - WorkflowTaskState state = WorkflowTaskState.valueOf(predicate[1]); - if (state == null) - { - return "Syntax Error. Unknown task state\n"; - } - query.setTaskState(state); - } - else if (predicate[0].equals("taskName")) - { - query.setTaskName(QName.createQName(predicate[1], namespaceService)); - } - else if (predicate[0].equals("taskActor")) - { - query.setActorId(predicate[1]); - } - else if (predicate[0].equals("processId")) - { - query.setProcessId(predicate[1]); - } - else if (predicate[0].equals("processName")) - { - query.setProcessName(QName.createQName(predicate[1], namespaceService)); - } - else if (predicate[0].equals("workflowDefinitionName")) - { - query.setWorkflowDefinitionName(predicate[1]); - } - else if (predicate[0].equals("processActive")) - { - Boolean active = Boolean.valueOf(predicate[1]); - query.setActive(active); - } - else if (predicate[0].equals("orderBy")) - { - String[] orderBy = predicate[1].split(","); - WorkflowTaskQuery.OrderBy[] queryOrderBy = new WorkflowTaskQuery.OrderBy[orderBy.length]; - for (int iOrderBy = 0; iOrderBy < orderBy.length; iOrderBy++) - { - queryOrderBy[iOrderBy] = WorkflowTaskQuery.OrderBy.valueOf(orderBy[iOrderBy]); - if (queryOrderBy[iOrderBy] == null) - { - return "Syntax Error. Unknown orderBy.\n"; - } - } - query.setOrderBy(queryOrderBy); - } - else - { - return "Syntax Error. Unknown query predicate.\n"; - } - } - else if (predicateName.length == 2) - { - if (predicateName[0].equals("task")) - { - taskProps.put(QName.createQName(predicateName[1], namespaceService), predicate[1]); - } - else if (predicateName[0].equals("process")) - { - procProps.put(QName.createQName(predicateName[1], namespaceService), predicate[1]); - } - else - { - return "Syntax Error. Unknown query predicate.\n"; - } - } - else - { - return "Syntax Error.\n"; - } - } - - if (taskProps.size() > 0) - { - query.setTaskCustomProps(taskProps); - } - if (procProps.size() > 0) - { - query.setProcessCustomProps(procProps); - } - - // execute query - List tasks = workflowService.queryTasks(query); - out.println("found " + tasks.size() + " tasks."); - for (WorkflowTask task : tasks) - { - out.println("task id: " + task.getId() + " , name: " + task.getName() + " , properties: " + task.getProperties().size() + ", process id: " + task.getPath().getInstance()); - } - } - - else - { - return "Syntax Error.\n"; - } - } - - else if (command[0].equals("deploy")) - { - if (command.length != 3) - { - return "Syntax Error.\n"; - } - ClassPathResource workflowDef = new ClassPathResource(command[2]); - WorkflowDeployment deployment = workflowService.deployDefinition(command[1], workflowDef.getInputStream(), MimetypeMap.MIMETYPE_XML); - WorkflowDefinition def = deployment.getDefinition(); - for (String problem : deployment.getProblems()) - { - out.println(problem); - } - out.println("deployed definition id: " + def.getId() + " , name: " + def.getName() + " , title: " + def.getTitle() + " , version: " + def.getVersion()); - currentDeployEngine = command[1]; - currentDeployResource = command[2]; - out.print(executeCommand("use definition " + def.getId())); - } - - else if (command[0].equals("redeploy")) - { - if (currentDeployResource == null) - { - return "nothing to redeploy\n"; - } - out.print(executeCommand("deploy " + currentDeployEngine + " " + currentDeployResource)); - } - - else if (command[0].equals("undeploy")) - { - if (command.length < 2) - { - return "Syntax Error.\n"; - } - if (command[1].equals("definition")) - { - if (command.length == 3) - { - workflowService.undeployDefinition(command[2]); - currentWorkflowDef = null; - currentPath = null; - out.print(executeCommand("show definitions")); - } - else if (command.length == 4) - { - if (command[2].equals("name")) - { - out.print("undeploying..."); - List defs = workflowService.getAllDefinitionsByName(command[3]); - for (WorkflowDefinition def: defs) - { - workflowService.undeployDefinition(def.getId()); - out.print(" v" + def.getVersion()); - } - out.println(""); - currentWorkflowDef = null; - currentPath = null; - out.print(executeCommand("show definitions all")); - } - else - { - return "Syntax Error.\n"; - } - } - else - { - return "Syntax Error.\n"; - } - } - else - { - return "Syntax Error.\n"; - } - } - - else if (command[0].equals("use")) - { - if (command.length == 1) - { - out.println("definition: " + ((currentWorkflowDef == null) ? "None" : currentWorkflowDef.getId() + " , name: " + currentWorkflowDef.getTitle() + " , version: " + currentWorkflowDef.getVersion())); - out.println("workflow: " + ((currentPath == null) ? "None" : currentPath.getInstance().getId() + " , active: " + currentPath.getInstance().isActive())); - out.println("path: " + ((currentPath == null) ? "None" : currentPath.getId() + " , node: " + currentPath.getNode().getTitle())); - } - else if (command.length > 1) - { - if (command[1].equals("definition")) - { - if (command.length != 3) - { - return "Syntax Error.\n"; - } - WorkflowDefinition def = workflowService.getDefinitionById(command[2]); - if (def == null) - { - return "Not found.\n"; - } - currentWorkflowDef = def; - currentPath = null; - out.print(executeCommand("use")); - } - - else if (command[1].equals("workflow")) - { - if (command.length != 3) - { - return "Syntax Error.\n"; - } - WorkflowInstance instance = workflowService.getWorkflowById(command[2]); - currentWorkflowDef = instance.getDefinition(); - currentPath = workflowService.getWorkflowPaths(instance.getId()).get(0); - out.print(executeCommand("use")); - } - else - { - return "Syntax Error.\n"; - } - } - } - - else if (command[0].equals("user")) - { - if (command.length == 2) - { - if (tenantService.isEnabled()) - { - tenantService.checkDomainUser(command[1]); - } - setCurrentUserName(command[1]); - } - out.println("using user " + getCurrentUserName()); - } - - else if (command[0].equals("start")) - { - Map params = new HashMap(); - for (int i = 1; i < command.length; i++) - { - String[] param = command[i].split("="); - QName qname = QName.createQName(param[0], namespaceService); - if (param.length == 1) - { - if (!vars.containsKey(qname)) - { - return "var " + qname + " not found.\n"; - } - params.put(qname, vars.get(qname)); - } - else if (param.length == 2) - { - params.put(qname, param[1]); - } - else - { - return "Syntax Error.\n"; - } - } - if (currentWorkflowDef == null) - { - return "Workflow definition not selected.\n"; - } - setupStartTaskParameters(currentWorkflowDef.getStartTaskDefinition().metadata, params); - WorkflowPath path = workflowService.startWorkflow(currentWorkflowDef.getId(), params); - endStartTaskForPath(path); - out.println("started workflow id: " + path.getInstance().getId() + " , def: " + path.getInstance().getDefinition().getTitle()); - currentPath = path; - out.print(interpretCommand("show transitions")); - } - - else if (command[0].equals("update")) - { - if (command.length < 3) - { - return "Syntax Error.\n"; - } - - if (command[1].equals("task")) - { - if (command.length < 4) - { - return "Syntax Error.\n"; - } - Map params = new HashMap(); - for (int i = 3; i < command.length; i++) - { - String[] param = command[i].split("="); - QName qname = QName.createQName(param[0], namespaceService); - if (param.length == 1) - { - if (!vars.containsKey(qname)) - { - return "var " + qname + " not found.\n"; - } - params.put(qname, vars.get(qname)); - } - else if (param.length == 2) - { - params.put(qname, param[1]); - } - else - { - return "Syntax Error.\n"; - } - } - WorkflowTask task = workflowService.updateTask(command[2], params, null, null); - out.println("updated task id: " + command[2] + ", properties: " + task.getProperties().size()); - } - else - { - return "Syntax Error.\n"; - } - } - - else if (command[0].equals("signal")) - { - if (command.length < 2) - { - return "Syntax Error.\n"; - } - WorkflowPath path = workflowService.signal(command[1], getTransition(command)); - out.println("signal sent - path id: " + path.getId()); - out.print(interpretCommand("show transitions")); - } - - else if (command[0].equals("event")) - { - if (command.length < 3) - { - return "Syntax Error.\n"; - } - WorkflowPath path = workflowService.fireEvent(command[1], command[2]); - out.println("event " + command[2] + " fired - path id: " + path.getId()); - out.print(interpretCommand("show transitions")); - } - - else if (command[0].equals("end")) - { - if (command.length < 3) - { - return "Syntax Error.\n"; - } - if (command[1].equals("task")) - { - WorkflowTask task = workflowService.endTask(command[2], (command.length == 4) ? command[3] : null); - out.println("signal sent - path id: " + task.getPath().getId()); - out.print(interpretCommand("show transitions")); - } - else if (command[1].equals("workflow")) - { - String workflowId = (command.length == 3) ? command[2] : (currentPath == null) ? null : currentPath.getInstance().getId(); - if (workflowId == null) - { - return "Syntax Error. Workflow Id not specified.\n"; - } - workflowService.cancelWorkflow(workflowId); - out.println("workflow " + workflowId + " cancelled."); - } - else - { - return "Syntax Error.\n"; - } - } - - else if (command[0].equals("delete")) - { - if (command.length < 2) - { - return "Syntax Error.\n"; - } - if (command[1].equals("workflow")) - { - String workflowId = (command.length == 3) ? command[2] : (currentPath == null) ? null : currentPath.getInstance().getId(); - if (workflowId == null) - { - return "Syntax Error. Workflow Id not specified.\n"; - } - workflowService.deleteWorkflow(workflowId); - out.println("workflow " + workflowId + " deleted."); - } - else if (command[1].equals("all")) - { - if (command.length < 3) - { - return "Syntax Error.\n"; - } - if (command[2].equals("workflows")) - { - if (command.length < 4) - { - return "Enter the command 'delete all workflows imeanit' to really delete all workflows\n"; - } - if (command[3].equals("imeanit")) - { - for (WorkflowDefinition def : workflowService.getAllDefinitions()) - { - List workflows = workflowService.getActiveWorkflows(def.getId()); - for (WorkflowInstance workflow : workflows) - { - workflowService.deleteWorkflow(workflow.getId()); - out.println("workflow " + workflow.getId() + " deleted."); - } - } - } - else - { - return "Syntax Error.\n"; - } - } - else - { - return "Syntax Error.\n"; - } - } - else - { - return "Syntax Error.\n"; - } - } - - else if (command[0].equals("var")) - { - if (command.length == 1) - { - for (Map.Entry entry : vars.entrySet()) - { - out.println(entry.getKey() + " = " + entry.getValue()); - } - } - else if (command.length == 2) - { - String[] param = command[1].split("="); - if (param.length == 0) - { - return "Syntax Error.\n"; - } - if (param.length == 1) - { - QName qname = QName.createQName(param[0], namespaceService); - vars.remove(qname); - out.println("deleted var " + qname); - } - else if (param.length == 2) - { - boolean multi = false; - if (param[0].endsWith("*")) - { - param[0] = param[0].substring(0, param[0].length() -1); - multi = true; - } - QName qname = QName.createQName(param[0], namespaceService); - String[] strValues = param[1].split(","); - if (!multi && strValues.length > 1) - { - return "Syntax Error.\n"; - } - if (!multi) - { - vars.put(qname, strValues[0]); - } - else - { - List values = new ArrayList(); - for (String strValue : strValues) - { - values.add(strValue); - } - vars.put(qname, (Serializable)values); - } - out.println("set var " + qname + " = " + vars.get(qname)); - } - else - { - return "Syntax Error.\n"; - } - } - else if (command.length == 4) - { - if (command[2].equals("person")) - { - boolean multi = false; - if (command[1].endsWith("*")) - { - command[1] = command[1].substring(0, command[1].length() -1); - multi = true; - } - QName qname = QName.createQName(command[1], namespaceService); - String[] strValues = command[3].split(","); - if (!multi && strValues.length > 1) - { - return "Syntax Error.\n"; - } - if (!multi) - { - NodeRef auth = personService.getPerson(strValues[0]); - vars.put(qname, auth); - } - else - { - List values = new ArrayList(); - for (String strValue : strValues) - { - NodeRef auth = personService.getPerson(strValue); - values.add(auth); - } - vars.put(qname, (Serializable)values); - } - out.println("set var " + qname + " = " + vars.get(qname)); - } - else if (command[2].equals("group")) - { - boolean multi = false; - if (command[1].endsWith("*")) - { - command[1] = command[1].substring(0, command[1].length() -1); - multi = true; - } - QName qname = QName.createQName(command[1], namespaceService); - String[] strValues = command[3].split(","); - if (!multi && strValues.length > 1) - { - return "Syntax Error.\n"; - } - if (!multi) - { - NodeRef auth = authorityDAO.getAuthorityNodeRefOrNull(strValues[0]); - if (auth == null) - { - throw new WorkflowException("Group " + strValues[0] + " does not exist."); - } - vars.put(qname, auth); - } - else - { - List values = new ArrayList(); - for (String strValue : strValues) - { - NodeRef auth = authorityDAO.getAuthorityNodeRefOrNull(strValue); - if (auth == null) - { - throw new WorkflowException("Group " + strValue + " does not exist."); - } - values.add(auth); - } - vars.put(qname, (Serializable)values); - } - out.println("set var " + qname + " = " + vars.get(qname)); - } - else if (command[2].equals("avmpackage")) - { - // lookup source folder of changes - AVMNodeDescriptor avmSource = avmService.lookup(-1, command[3]); - if (avmSource == null || !avmSource.isDirectory()) - { - return command[3] + " must refer to a directory."; - } - - // create container for avm workflow packages - String packagesPath = "workflow-system:/packages"; - AVMNodeDescriptor packagesDesc = avmService.lookup(-1, packagesPath); - if (packagesDesc == null) - { - avmService.createStore("workflow-system"); - avmService.createDirectory("workflow-system:/", "packages"); - } - - // create package (layered to target, if target is specified) - String packageName = GUID.generate(); - String avmSourceIndirection = avmSource.getIndirection(); - if (avmSourceIndirection != null) - { - avmService.createLayeredDirectory(avmSourceIndirection, packagesPath, packageName); - List diff = avmSyncService.compare(-1, avmSource.getPath(), -1, packagesPath + "/" + packageName, null); - avmSyncService.update(diff, null, true, true, false, false, null, null); - } - else - { - // copy source folder to package folder - avmService.copy(-1, avmSource.getPath(), packagesPath, packageName); - } - - // convert package to workflow package - AVMNodeDescriptor packageDesc = avmService.lookup(-1, packagesPath + "/" + packageName); - NodeRef packageNodeRef = workflowService.createPackage(AVMNodeConverter.ToNodeRef(-1, packageDesc.getPath())); - nodeService.setProperty(packageNodeRef, WorkflowModel.PROP_IS_SYSTEM_PACKAGE, true); - QName qname = QName.createQName(command[1], namespaceService); - vars.put(qname, packageNodeRef); - out.println("set var " + qname + " = " + vars.get(qname)); - } - else if (command[2].equals("package")) - { - QName qname = QName.createQName(command[1], namespaceService); - int number = new Integer(command[3]); - NodeRef packageNodeRef = workflowService.createPackage(null); - for (int i = 0; i < number; i++) - { - FileInfo fileInfo = fileFolderService.create(packageNodeRef, "Content" + i, ContentModel.TYPE_CONTENT); - ContentWriter writer = fileFolderService.getWriter(fileInfo.getNodeRef()); - writer.putContent("Content" + i); - } - vars.put(qname, packageNodeRef); - out.println("set var " + qname + " = " + vars.get(qname)); - } - else - { - return "Syntax Error.\n"; - } - } - else - { - return "Syntax Error.\n"; - } - } - - else - { - return "Syntax Error.\n"; - } - - out.flush(); - String retVal = new String(bout.toByteArray()); - out.close(); - return retVal; - } - - private String getTransition(String[] command) - { - int length = command.length; - if(length <3) - { - return null; - } - // Transition name may contain spaces - StringBuilder builder = new StringBuilder(command[2]); - int i = 3; - while(i params) - { - // build a complete anonymous type for the start task - List aspects = typeDef.getDefaultAspects(); - List aspectNames = new ArrayList(aspects.size()); - getMandatoryAspects(typeDef, aspectNames); - ClassDefinition startTaskDef = dictionaryService.getAnonymousType(typeDef.getName(), aspectNames); - - // apply default values - Map propertyDefs = startTaskDef.getProperties(); - for (Map.Entry entry : propertyDefs.entrySet()) - { - String defaultValue = entry.getValue().getDefaultValue(); - - if (params.get(entry.getKey()) == null) - { - if (defaultValue != null) - { - params.put(entry.getKey(), (Serializable)DefaultTypeConverter.INSTANCE.convert(entry.getValue().getDataType(), defaultValue)); - } - } - else - { - params.put(entry.getKey(), (Serializable)DefaultTypeConverter.INSTANCE.convert(entry.getValue().getDataType(), params.get(entry.getKey()))); - } - } - - if (params.containsKey(WorkflowModel.ASSOC_ASSIGNEE)) - { - String value = (String)params.get(WorkflowModel.ASSOC_ASSIGNEE); - ArrayList assignees = new ArrayList(); - assignees.add(personService.getPerson(value)); - params.put(WorkflowModel.ASSOC_ASSIGNEE, assignees); - } - - params.put(WorkflowModel.ASSOC_PACKAGE, workflowService.createPackage(null)); - } - - private void getMandatoryAspects(ClassDefinition classDef, List aspects) - { - for (AspectDefinition aspect : classDef.getDefaultAspects()) - { - QName aspectName = aspect.getName(); - if (!aspects.contains(aspectName)) - { - aspects.add(aspect.getName()); - getMandatoryAspects(aspect, aspects); - } - } - } - - private void endStartTaskForPath(WorkflowPath path) - { - if (path != null) - { - List tasks = this.workflowService.getTasksForWorkflowPath(path.id); - if (tasks.size() == 1) - { - WorkflowTask startTask = tasks.get(0); - - if (startTask.state == WorkflowTaskState.IN_PROGRESS) - { - // end the start task to trigger the first 'proper' - // task in the workflow - this.workflowService.endTask(startTask.id, 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.workflow; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.admin.BaseInterpreter; +import org.alfresco.repo.avm.AVMNodeConverter; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authority.AuthorityDAO; +import org.alfresco.repo.tenant.TenantService; +import org.alfresco.service.cmr.avm.AVMNodeDescriptor; +import org.alfresco.service.cmr.avm.AVMService; +import org.alfresco.service.cmr.avmsync.AVMDifference; +import org.alfresco.service.cmr.avmsync.AVMSyncService; +import org.alfresco.service.cmr.dictionary.AspectDefinition; +import org.alfresco.service.cmr.dictionary.ClassDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.service.cmr.dictionary.TypeDefinition; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.model.FileInfo; +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.datatype.DefaultTypeConverter; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.cmr.workflow.WorkflowDefinition; +import org.alfresco.service.cmr.workflow.WorkflowDeployment; +import org.alfresco.service.cmr.workflow.WorkflowException; +import org.alfresco.service.cmr.workflow.WorkflowInstance; +import org.alfresco.service.cmr.workflow.WorkflowPath; +import org.alfresco.service.cmr.workflow.WorkflowService; +import org.alfresco.service.cmr.workflow.WorkflowTask; +import org.alfresco.service.cmr.workflow.WorkflowTaskQuery; +import org.alfresco.service.cmr.workflow.WorkflowTaskState; +import org.alfresco.service.cmr.workflow.WorkflowTimer; +import org.alfresco.service.cmr.workflow.WorkflowTransition; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.GUID; +import org.springframework.context.ApplicationEvent; +import org.springframework.core.io.ClassPathResource; +import org.springframework.extensions.surf.util.I18NUtil; + +/** + * An interactive console for Workflows. + * + * @author davidc + */ +public class WorkflowInterpreter extends BaseInterpreter +{ + // Service dependencies + private WorkflowService workflowService; + private NamespaceService namespaceService; + private NodeService nodeService; + private AuthorityDAO authorityDAO; + private AVMService avmService; + private AVMSyncService avmSyncService; + private PersonService personService; + private FileFolderService fileFolderService; + private TenantService tenantService; + private DictionaryService dictionaryService; + + /** + * Current context + */ + private WorkflowDefinition currentWorkflowDef = null; + private WorkflowPath currentPath = null; + private String currentDeployResource = null; + private String currentDeployEngine = null; + + + /** + * Variables + */ + private Map vars = new HashMap(); + + + + + /* (non-Javadoc) + * @see org.springframework.extensions.surf.util.AbstractLifecycleBean#onBootstrap(org.springframework.getContext().ApplicationEvent) + */ + @Override + protected void onBootstrap(ApplicationEvent event) + { + //NOOP + } + + /* (non-Javadoc) + * @see org.springframework.extensions.surf.util.AbstractLifecycleBean#onShutdown(org.springframework.getContext().ApplicationEvent) + */ + @Override + protected void onShutdown(ApplicationEvent event) + { + // NOOP + } + + /** + * @param workflowService The Workflow Service + */ + public void setWorkflowService(WorkflowService workflowService) + { + this.workflowService = workflowService; + } + + /** + * @param nodeService The Node Service + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * @param tenantService The Tenant Service + */ + public void setTenantService(TenantService tenantService) + { + this.tenantService = tenantService; + } + + /** + * @param dictionaryService dictionaryService + */ + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + /** + * @param avmService The AVM Service + */ + public void setAVMService(AVMService avmService) + { + this.avmService = avmService; + } + + /** + * @param avmSyncService The AVM Sync Service + */ + public void setAVMSyncService(AVMSyncService avmSyncService) + { + this.avmSyncService = avmSyncService; + } + + /** + * @param namespaceService namespaceService + */ + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + /** + * @param personService personService + */ + public void setPersonService(PersonService personService) + { + this.personService = personService; + } + + /** + * @param transactionService transactionService + */ + @Override + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + /** + * @param authorityDAO authorityDAO + */ + public void setAuthorityDAO(AuthorityDAO authorityDAO) + { + this.authorityDAO = authorityDAO; + } + + /** + * @param fileFolderService fileFolderService + */ + public void setFileFolderService(FileFolderService fileFolderService) + { + this.fileFolderService = fileFolderService; + } + + /** + * Main entry point. + */ + public static void main(String[] args) + { + runMain("workflowInterpreter"); + } + + @Override + protected boolean hasAuthority(String username) + { + // admin can change to any user (via worklow command "user ") + return true; + } + + /** + * Execute a single command using the BufferedReader passed in for any data needed. + * + * TODO: Use decent parser! + * + * @param line The unparsed command + * @return The textual output of the command. + */ + @Override + protected String executeCommand(String line) throws IOException + { + String[] command = line.split(" "); + if (command.length == 0) + { + command = new String[1]; + command[0] = line; + } + + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(bout); + + // repeat last command? + if (command[0].equals("r")) + { + if (lastCommand == null) + { + return "No command entered yet."; + } + return "repeating command " + lastCommand + "\n\n" + executeCommand(lastCommand); + } + + // remember last command + lastCommand = line; + + // execute command + if (command[0].equals("help")) + { + String helpFile = I18NUtil.getMessage("workflow_console.help"); + ClassPathResource helpResource = new ClassPathResource(helpFile); + byte[] helpBytes = new byte[500]; + InputStream helpStream = helpResource.getInputStream(); + try + { + int read = helpStream.read(helpBytes); + while (read != -1) + { + bout.write(helpBytes, 0, read); + read = helpStream.read(helpBytes); + } + } + finally + { + helpStream.close(); + } + } + + else if (command[0].equals("show")) + { + if (command.length < 2) + { + return "Syntax Error.\n"; + } + + else if (command[1].equals("file")) + { + if (command.length != 3) + { + return "Syntax Error.\n"; + } + ClassPathResource file = new ClassPathResource(command[2]); + InputStream fileStream = file.getInputStream(); + byte[] fileBytes = new byte[500]; + try + { + int read = fileStream.read(fileBytes); + while (read != -1) + { + bout.write(fileBytes, 0, read); + read = fileStream.read(fileBytes); + } + } + finally + { + fileStream.close(); + } + out.println(); + } + + else if (command[1].equals("definitions")) + { + List defs = null; + if (command.length == 3) + { + if (command[2].equals("all")) + { + defs = workflowService.getAllDefinitions(); + } + else + { + return "Syntax Error.\n"; + } + } + else + { + defs = workflowService.getDefinitions(); + } + for (WorkflowDefinition def : defs) + { + out.println("id: " + def.getId() + " , name: " + def.getName() + " , title: " + def.getTitle() + " , version: " + def.getVersion()); + } + } + + else if (command[1].equals("workflows")) + { + String id = (currentWorkflowDef != null) ? currentWorkflowDef.getId() : null; + if (id == null && command.length == 2) + { + return "workflow definition not in use. Enter command 'show workflows all' or 'use '.\n"; + } + if (command.length == 3) + { + if (command[2].equals("all")) + { + id = "all"; + } + else + { + return "Syntax Error.\n"; + } + } + + if ("all".equals(id)) + { + for (WorkflowDefinition def : workflowService.getAllDefinitions()) + { + List workflows = workflowService.getActiveWorkflows(def.getId()); + for (WorkflowInstance workflow : workflows) + { + out.println("id: " + workflow.getId() + " , desc: " + workflow.getDescription() + " , start date: " + workflow.getStartDate() + " , def: " + workflow.getDefinition().getName() + " v" + workflow.getDefinition().getVersion()); + } + } + } + else + { + List workflows = workflowService.getActiveWorkflows(id); + for (WorkflowInstance workflow : workflows) + { + out.println("id: " + workflow.getId() + " , desc: " + workflow.getDescription() + " , start date: " + workflow.getStartDate() + " , def: " + workflow.getDefinition().getName()); + } + } + } + + else if (command[1].equals("paths")) + { + String workflowId = (command.length == 3) ? command[2] : (currentPath == null) ? null : currentPath.getInstance().getId(); + if (workflowId == null) + { + return "Syntax Error. Workflow Id not specified.\n"; + } + List paths = workflowService.getWorkflowPaths(workflowId); + for (WorkflowPath path : paths) + { + out.println("path id: " + path.getId() + " , node: " + path.getNode().getName()); + } + } + + else if (command[1].equals("tasks")) + { + String pathId = (command.length == 3) ? command[2] : (currentPath == null) ? null : currentPath.getId(); + if (pathId == null) + { + return "Syntax Error. Path Id not specified.\n"; + } + List tasks = workflowService.getTasksForWorkflowPath(pathId); + for (WorkflowTask task : tasks) + { + out.println("task id: " + task.getId() + " , name: " + task.getName() + " , properties: " + task.getProperties().size()); + } + } + + else if (command[1].equals("transitions")) + { + String workflowId = (command.length == 3) ? command[2] : (currentPath == null) ? null : currentPath.getInstance().getId(); + if (workflowId == null) + { + return "Syntax Error. Workflow Id not specified.\n"; + } + List paths = workflowService.getWorkflowPaths(workflowId); + if (paths.size() == 0) + { + out.println("no further transitions"); + } + for (WorkflowPath path : paths) + { + out.println("path: " + path.getId() + " , node: " + path.getNode().getName() + " , active: " + path.isActive()); + List tasks = workflowService.getTasksForWorkflowPath(path.getId()); + for (WorkflowTask task : tasks) + { + out.println(" task id: " + task.getId() + " , name: " + task.getName() + ", title: " + task.getTitle() + " , desc: " + task.getDescription() + " , properties: " + task.getProperties().size()); + } + for (WorkflowTransition transition : path.getNode().getTransitions()) + { + out.println(" transition id: " + ((transition.getId() == null || transition.getId().equals("")) ? "[default]" : transition.getId()) + " , title: " + transition.getTitle()); + } + } + } + + else if (command[1].equals("timers")) + { + String id = (currentWorkflowDef != null) ? currentWorkflowDef.getId() : null; + if (id == null && command.length == 2) + { + return "workflow definition not in use. Enter command 'show timers all' or 'use '.\n"; + } + if (command.length == 3) + { + if (command[2].equals("all")) + { + id = "all"; + } + else + { + return "Syntax Error.\n"; + } + } + + List timers = new ArrayList(); + + if ("all".equals(id)) + { + for (WorkflowDefinition def : workflowService.getAllDefinitions()) + { + List workflows = workflowService.getActiveWorkflows(def.getId()); + for (WorkflowInstance workflow : workflows) + { + timers.addAll(workflowService.getTimers(workflow.getId())); + } + } + } + else + { + List workflows = workflowService.getActiveWorkflows(id); + for (WorkflowInstance workflow : workflows) + { + timers.addAll(workflowService.getTimers(workflow.getId())); + } + } + + for (WorkflowTimer timer : timers) + { + out.print("id: " + timer.getId() + " , name: " + timer.getName() + " , due date: " + timer.getDueDate() + " , path: " + timer.getPath().getId() + " , node: " + timer.getPath().getNode().getName() + " , process: " + timer.getPath().getInstance().getId()); + if (timer.getTask() != null) + { + out.print(" , task: " + timer.getTask().getName() + "(" + timer.getTask().getId() + ")"); + } + out.println(); + if (timer.getError() != null) + { + out.println("error executing timer id " + timer.getId()); + out.println(timer.getError()); + } + } + } + + else if (command[1].equals("my")) + { + if (command.length != 3) + { + return "Syntax Error.\n"; + } + + if (command[2].equals("tasks")) + { + out.println(AuthenticationUtil.getRunAsUser() + ":"); + List tasks = workflowService.getAssignedTasks(AuthenticationUtil.getRunAsUser(), WorkflowTaskState.IN_PROGRESS); + for (WorkflowTask task : tasks) + { + out.println("id: " + task.getId() + " , name: " + task.getName() + " , properties: " + task.getProperties().size() + " , workflow: " + task.getPath().getInstance().getId() + " , path: " + task.getPath().getId()); + } + } + + else if (command[2].equals("completed")) + { + out.println(AuthenticationUtil.getRunAsUser() + ":"); + List tasks = workflowService.getAssignedTasks(AuthenticationUtil.getRunAsUser(), WorkflowTaskState.COMPLETED); + for (WorkflowTask task : tasks) + { + out.println("id: " + task.getId() + " , name " + task.getName() + " , properties: " + task.getProperties().size() + " , workflow: " + task.getPath().getInstance().getId() + " , path: " + task.getPath().getId()); + } + } + + else if (command[2].equals("pooled")) + { + out.println(AuthenticationUtil.getRunAsUser() + ":"); + List tasks = workflowService.getPooledTasks(AuthenticationUtil.getRunAsUser()); + for (WorkflowTask task : tasks) + { + out.println("id: " + task.getId() + " , name " + task.getName() + " , properties: " + task.getProperties().size() + " , workflow: " + task.getPath().getInstance().getId() + " , path: " + task.getPath().getId()); + } + } + + else + { + return "Syntax Error.\n"; + } + } + else + { + return "Syntax Error.\n"; + } + } + + else if (command[0].equals("desc")) + { + if (command.length < 2) + { + return "Syntax Error.\n"; + } + + if (command[1].equals("task")) + { + if (command.length != 3) + { + return "Syntax Error.\n"; + } + WorkflowTask task = workflowService.getTaskById(command[2]); + out.println("id: " + task.getId()); + out.println("name: " + task.getName()); + out.println("title: " + task.getTitle()); + out.println("description: " + task.getDescription()); + out.println("state: " + task.getState()); + out.println("path: " + task.getPath().getId()); + out.println("transitions: " + task.getDefinition().getNode().getTransitions().length); + for (WorkflowTransition transition : task.getDefinition().getNode().getTransitions()) + { + out.println(" transition: " + ((transition.getId() == null || transition.getId().equals("")) ? "[default]" : transition.getId()) + " , title: " + transition.getTitle() + " , desc: " + transition.getDescription()); + } + out.println("properties: " + task.getProperties().size()); + for (Map.Entry prop : task.getProperties().entrySet()) + { + out.println(" " + prop.getKey() + " = " + prop.getValue()); + } + } + + else if (command[1].equals("workflow")) + { + if (command.length != 3) + { + return "Syntax Error.\n"; + } + WorkflowInstance workflow = workflowService.getWorkflowById(command[2]); + out.println("definition: " + workflow.getDefinition().getName()); + out.println("id: " + workflow.getId()); + out.println("description: " + workflow.getDescription()); + out.println("active: " + workflow.isActive()); + out.println("start date: " + workflow.getStartDate()); + out.println("end date: " + workflow.getEndDate()); + out.println("initiator: " + workflow.getInitiator()); + out.println("context: " + workflow.getContext()); + out.println("package: " + workflow.getWorkflowPackage()); + } + + else if (command[1].equals("path")) + { + if (command.length != 3) + { + return "Syntax Error.\n"; + } + Map properties = workflowService.getPathProperties(command[2]); + out.println("path: " + command[1]); + out.println("properties: " + properties.size()); + for (Map.Entry prop : properties.entrySet()) + { + out.println(" " + prop.getKey() + " = " + prop.getValue()); + } + } + + else + { + return "Syntax Error.\n"; + } + } + + else if (command[0].equals("query")) + { + if (command.length < 2) + { + return "Syntax Error.\n"; + } + + if (command[1].equals("task")) + { + // build query + WorkflowTaskQuery query = new WorkflowTaskQuery(); + Map taskProps = new HashMap(); + Map procProps = new HashMap(); + + for (int i = 2; i < command.length; i++) + { + String[] predicate = command[i].split("="); + if (predicate.length == 1) + { + return "Syntax Error.\n"; + } + String[] predicateName = predicate[0].split("\\."); + if (predicateName.length == 1) + { + if (predicate[0].equals("taskId")) + { + query.setTaskId(predicate[1]); + } + else if (predicate[0].equals("taskState")) + { + WorkflowTaskState state = WorkflowTaskState.valueOf(predicate[1]); + if (state == null) + { + return "Syntax Error. Unknown task state\n"; + } + query.setTaskState(state); + } + else if (predicate[0].equals("taskName")) + { + query.setTaskName(QName.createQName(predicate[1], namespaceService)); + } + else if (predicate[0].equals("taskActor")) + { + query.setActorId(predicate[1]); + } + else if (predicate[0].equals("processId")) + { + query.setProcessId(predicate[1]); + } + else if (predicate[0].equals("processName")) + { + query.setProcessName(QName.createQName(predicate[1], namespaceService)); + } + else if (predicate[0].equals("workflowDefinitionName")) + { + query.setWorkflowDefinitionName(predicate[1]); + } + else if (predicate[0].equals("processActive")) + { + Boolean active = Boolean.valueOf(predicate[1]); + query.setActive(active); + } + else if (predicate[0].equals("orderBy")) + { + String[] orderBy = predicate[1].split(","); + WorkflowTaskQuery.OrderBy[] queryOrderBy = new WorkflowTaskQuery.OrderBy[orderBy.length]; + for (int iOrderBy = 0; iOrderBy < orderBy.length; iOrderBy++) + { + queryOrderBy[iOrderBy] = WorkflowTaskQuery.OrderBy.valueOf(orderBy[iOrderBy]); + if (queryOrderBy[iOrderBy] == null) + { + return "Syntax Error. Unknown orderBy.\n"; + } + } + query.setOrderBy(queryOrderBy); + } + else + { + return "Syntax Error. Unknown query predicate.\n"; + } + } + else if (predicateName.length == 2) + { + if (predicateName[0].equals("task")) + { + taskProps.put(QName.createQName(predicateName[1], namespaceService), predicate[1]); + } + else if (predicateName[0].equals("process")) + { + procProps.put(QName.createQName(predicateName[1], namespaceService), predicate[1]); + } + else + { + return "Syntax Error. Unknown query predicate.\n"; + } + } + else + { + return "Syntax Error.\n"; + } + } + + if (taskProps.size() > 0) + { + query.setTaskCustomProps(taskProps); + } + if (procProps.size() > 0) + { + query.setProcessCustomProps(procProps); + } + + // execute query + List tasks = workflowService.queryTasks(query); + out.println("found " + tasks.size() + " tasks."); + for (WorkflowTask task : tasks) + { + out.println("task id: " + task.getId() + " , name: " + task.getName() + " , properties: " + task.getProperties().size() + ", process id: " + task.getPath().getInstance()); + } + } + + else + { + return "Syntax Error.\n"; + } + } + + else if (command[0].equals("deploy")) + { + if (command.length != 3) + { + return "Syntax Error.\n"; + } + ClassPathResource workflowDef = new ClassPathResource(command[2]); + WorkflowDeployment deployment = workflowService.deployDefinition(command[1], workflowDef.getInputStream(), MimetypeMap.MIMETYPE_XML); + WorkflowDefinition def = deployment.getDefinition(); + for (String problem : deployment.getProblems()) + { + out.println(problem); + } + out.println("deployed definition id: " + def.getId() + " , name: " + def.getName() + " , title: " + def.getTitle() + " , version: " + def.getVersion()); + currentDeployEngine = command[1]; + currentDeployResource = command[2]; + out.print(executeCommand("use definition " + def.getId())); + } + + else if (command[0].equals("redeploy")) + { + if (currentDeployResource == null) + { + return "nothing to redeploy\n"; + } + out.print(executeCommand("deploy " + currentDeployEngine + " " + currentDeployResource)); + } + + else if (command[0].equals("undeploy")) + { + if (command.length < 2) + { + return "Syntax Error.\n"; + } + if (command[1].equals("definition")) + { + if (command.length == 3) + { + workflowService.undeployDefinition(command[2]); + currentWorkflowDef = null; + currentPath = null; + out.print(executeCommand("show definitions")); + } + else if (command.length == 4) + { + if (command[2].equals("name")) + { + out.print("undeploying..."); + List defs = workflowService.getAllDefinitionsByName(command[3]); + for (WorkflowDefinition def: defs) + { + workflowService.undeployDefinition(def.getId()); + out.print(" v" + def.getVersion()); + } + out.println(""); + currentWorkflowDef = null; + currentPath = null; + out.print(executeCommand("show definitions all")); + } + else + { + return "Syntax Error.\n"; + } + } + else + { + return "Syntax Error.\n"; + } + } + else + { + return "Syntax Error.\n"; + } + } + + else if (command[0].equals("use")) + { + if (command.length == 1) + { + out.println("definition: " + ((currentWorkflowDef == null) ? "None" : currentWorkflowDef.getId() + " , name: " + currentWorkflowDef.getTitle() + " , version: " + currentWorkflowDef.getVersion())); + out.println("workflow: " + ((currentPath == null) ? "None" : currentPath.getInstance().getId() + " , active: " + currentPath.getInstance().isActive())); + out.println("path: " + ((currentPath == null) ? "None" : currentPath.getId() + " , node: " + currentPath.getNode().getTitle())); + } + else if (command.length > 1) + { + if (command[1].equals("definition")) + { + if (command.length != 3) + { + return "Syntax Error.\n"; + } + WorkflowDefinition def = workflowService.getDefinitionById(command[2]); + if (def == null) + { + return "Not found.\n"; + } + currentWorkflowDef = def; + currentPath = null; + out.print(executeCommand("use")); + } + + else if (command[1].equals("workflow")) + { + if (command.length != 3) + { + return "Syntax Error.\n"; + } + WorkflowInstance instance = workflowService.getWorkflowById(command[2]); + currentWorkflowDef = instance.getDefinition(); + currentPath = workflowService.getWorkflowPaths(instance.getId()).get(0); + out.print(executeCommand("use")); + } + else + { + return "Syntax Error.\n"; + } + } + } + + else if (command[0].equals("user")) + { + if (command.length == 2) + { + if (tenantService.isEnabled()) + { + tenantService.checkDomainUser(command[1]); + } + setCurrentUserName(command[1]); + } + out.println("using user " + getCurrentUserName()); + } + + else if (command[0].equals("start")) + { + Map params = new HashMap(); + for (int i = 1; i < command.length; i++) + { + String[] param = command[i].split("="); + QName qname = QName.createQName(param[0], namespaceService); + if (param.length == 1) + { + if (!vars.containsKey(qname)) + { + return "var " + qname + " not found.\n"; + } + params.put(qname, vars.get(qname)); + } + else if (param.length == 2) + { + params.put(qname, param[1]); + } + else + { + return "Syntax Error.\n"; + } + } + if (currentWorkflowDef == null) + { + return "Workflow definition not selected.\n"; + } + setupStartTaskParameters(currentWorkflowDef.getStartTaskDefinition().metadata, params); + WorkflowPath path = workflowService.startWorkflow(currentWorkflowDef.getId(), params); + endStartTaskForPath(path); + out.println("started workflow id: " + path.getInstance().getId() + " , def: " + path.getInstance().getDefinition().getTitle()); + currentPath = path; + out.print(interpretCommand("show transitions")); + } + + else if (command[0].equals("update")) + { + if (command.length < 3) + { + return "Syntax Error.\n"; + } + + if (command[1].equals("task")) + { + if (command.length < 4) + { + return "Syntax Error.\n"; + } + Map params = new HashMap(); + for (int i = 3; i < command.length; i++) + { + String[] param = command[i].split("="); + QName qname = QName.createQName(param[0], namespaceService); + if (param.length == 1) + { + if (!vars.containsKey(qname)) + { + return "var " + qname + " not found.\n"; + } + params.put(qname, vars.get(qname)); + } + else if (param.length == 2) + { + params.put(qname, param[1]); + } + else + { + return "Syntax Error.\n"; + } + } + WorkflowTask task = workflowService.updateTask(command[2], params, null, null); + out.println("updated task id: " + command[2] + ", properties: " + task.getProperties().size()); + } + else + { + return "Syntax Error.\n"; + } + } + + else if (command[0].equals("signal")) + { + if (command.length < 2) + { + return "Syntax Error.\n"; + } + WorkflowPath path = workflowService.signal(command[1], getTransition(command)); + out.println("signal sent - path id: " + path.getId()); + out.print(interpretCommand("show transitions")); + } + + else if (command[0].equals("event")) + { + if (command.length < 3) + { + return "Syntax Error.\n"; + } + WorkflowPath path = workflowService.fireEvent(command[1], command[2]); + out.println("event " + command[2] + " fired - path id: " + path.getId()); + out.print(interpretCommand("show transitions")); + } + + else if (command[0].equals("end")) + { + if (command.length < 3) + { + return "Syntax Error.\n"; + } + if (command[1].equals("task")) + { + WorkflowTask task = workflowService.endTask(command[2], (command.length == 4) ? command[3] : null); + out.println("signal sent - path id: " + task.getPath().getId()); + out.print(interpretCommand("show transitions")); + } + else if (command[1].equals("workflow")) + { + String workflowId = (command.length == 3) ? command[2] : (currentPath == null) ? null : currentPath.getInstance().getId(); + if (workflowId == null) + { + return "Syntax Error. Workflow Id not specified.\n"; + } + workflowService.cancelWorkflow(workflowId); + out.println("workflow " + workflowId + " cancelled."); + } + else + { + return "Syntax Error.\n"; + } + } + + else if (command[0].equals("delete")) + { + if (command.length < 2) + { + return "Syntax Error.\n"; + } + if (command[1].equals("workflow")) + { + String workflowId = (command.length == 3) ? command[2] : (currentPath == null) ? null : currentPath.getInstance().getId(); + if (workflowId == null) + { + return "Syntax Error. Workflow Id not specified.\n"; + } + workflowService.deleteWorkflow(workflowId); + out.println("workflow " + workflowId + " deleted."); + } + else if (command[1].equals("all")) + { + if (command.length < 3) + { + return "Syntax Error.\n"; + } + if (command[2].equals("workflows")) + { + if (command.length < 4) + { + return "Enter the command 'delete all workflows imeanit' to really delete all workflows\n"; + } + if (command[3].equals("imeanit")) + { + for (WorkflowDefinition def : workflowService.getAllDefinitions()) + { + List workflows = workflowService.getActiveWorkflows(def.getId()); + for (WorkflowInstance workflow : workflows) + { + workflowService.deleteWorkflow(workflow.getId()); + out.println("workflow " + workflow.getId() + " deleted."); + } + } + } + else + { + return "Syntax Error.\n"; + } + } + else + { + return "Syntax Error.\n"; + } + } + else + { + return "Syntax Error.\n"; + } + } + + else if (command[0].equals("var")) + { + if (command.length == 1) + { + for (Map.Entry entry : vars.entrySet()) + { + out.println(entry.getKey() + " = " + entry.getValue()); + } + } + else if (command.length == 2) + { + String[] param = command[1].split("="); + if (param.length == 0) + { + return "Syntax Error.\n"; + } + if (param.length == 1) + { + QName qname = QName.createQName(param[0], namespaceService); + vars.remove(qname); + out.println("deleted var " + qname); + } + else if (param.length == 2) + { + boolean multi = false; + if (param[0].endsWith("*")) + { + param[0] = param[0].substring(0, param[0].length() -1); + multi = true; + } + QName qname = QName.createQName(param[0], namespaceService); + String[] strValues = param[1].split(","); + if (!multi && strValues.length > 1) + { + return "Syntax Error.\n"; + } + if (!multi) + { + vars.put(qname, strValues[0]); + } + else + { + List values = new ArrayList(); + for (String strValue : strValues) + { + values.add(strValue); + } + vars.put(qname, (Serializable)values); + } + out.println("set var " + qname + " = " + vars.get(qname)); + } + else + { + return "Syntax Error.\n"; + } + } + else if (command.length == 4) + { + if (command[2].equals("person")) + { + boolean multi = false; + if (command[1].endsWith("*")) + { + command[1] = command[1].substring(0, command[1].length() -1); + multi = true; + } + QName qname = QName.createQName(command[1], namespaceService); + String[] strValues = command[3].split(","); + if (!multi && strValues.length > 1) + { + return "Syntax Error.\n"; + } + if (!multi) + { + NodeRef auth = personService.getPerson(strValues[0]); + vars.put(qname, auth); + } + else + { + List values = new ArrayList(); + for (String strValue : strValues) + { + NodeRef auth = personService.getPerson(strValue); + values.add(auth); + } + vars.put(qname, (Serializable)values); + } + out.println("set var " + qname + " = " + vars.get(qname)); + } + else if (command[2].equals("group")) + { + boolean multi = false; + if (command[1].endsWith("*")) + { + command[1] = command[1].substring(0, command[1].length() -1); + multi = true; + } + QName qname = QName.createQName(command[1], namespaceService); + String[] strValues = command[3].split(","); + if (!multi && strValues.length > 1) + { + return "Syntax Error.\n"; + } + if (!multi) + { + NodeRef auth = authorityDAO.getAuthorityNodeRefOrNull(strValues[0]); + if (auth == null) + { + throw new WorkflowException("Group " + strValues[0] + " does not exist."); + } + vars.put(qname, auth); + } + else + { + List values = new ArrayList(); + for (String strValue : strValues) + { + NodeRef auth = authorityDAO.getAuthorityNodeRefOrNull(strValue); + if (auth == null) + { + throw new WorkflowException("Group " + strValue + " does not exist."); + } + values.add(auth); + } + vars.put(qname, (Serializable)values); + } + out.println("set var " + qname + " = " + vars.get(qname)); + } + else if (command[2].equals("avmpackage")) + { + // lookup source folder of changes + AVMNodeDescriptor avmSource = avmService.lookup(-1, command[3]); + if (avmSource == null || !avmSource.isDirectory()) + { + return command[3] + " must refer to a directory."; + } + + // create container for avm workflow packages + String packagesPath = "workflow-system:/packages"; + AVMNodeDescriptor packagesDesc = avmService.lookup(-1, packagesPath); + if (packagesDesc == null) + { + avmService.createStore("workflow-system"); + avmService.createDirectory("workflow-system:/", "packages"); + } + + // create package (layered to target, if target is specified) + String packageName = GUID.generate(); + String avmSourceIndirection = avmSource.getIndirection(); + if (avmSourceIndirection != null) + { + avmService.createLayeredDirectory(avmSourceIndirection, packagesPath, packageName); + List diff = avmSyncService.compare(-1, avmSource.getPath(), -1, packagesPath + "/" + packageName, null); + avmSyncService.update(diff, null, true, true, false, false, null, null); + } + else + { + // copy source folder to package folder + avmService.copy(-1, avmSource.getPath(), packagesPath, packageName); + } + + // convert package to workflow package + AVMNodeDescriptor packageDesc = avmService.lookup(-1, packagesPath + "/" + packageName); + NodeRef packageNodeRef = workflowService.createPackage(AVMNodeConverter.ToNodeRef(-1, packageDesc.getPath())); + nodeService.setProperty(packageNodeRef, WorkflowModel.PROP_IS_SYSTEM_PACKAGE, true); + QName qname = QName.createQName(command[1], namespaceService); + vars.put(qname, packageNodeRef); + out.println("set var " + qname + " = " + vars.get(qname)); + } + else if (command[2].equals("package")) + { + QName qname = QName.createQName(command[1], namespaceService); + int number = new Integer(command[3]); + NodeRef packageNodeRef = workflowService.createPackage(null); + for (int i = 0; i < number; i++) + { + FileInfo fileInfo = fileFolderService.create(packageNodeRef, "Content" + i, ContentModel.TYPE_CONTENT); + ContentWriter writer = fileFolderService.getWriter(fileInfo.getNodeRef()); + writer.putContent("Content" + i); + } + vars.put(qname, packageNodeRef); + out.println("set var " + qname + " = " + vars.get(qname)); + } + else + { + return "Syntax Error.\n"; + } + } + else + { + return "Syntax Error.\n"; + } + } + + else + { + return "Syntax Error.\n"; + } + + out.flush(); + String retVal = new String(bout.toByteArray()); + out.close(); + return retVal; + } + + private String getTransition(String[] command) + { + int length = command.length; + if(length <3) + { + return null; + } + // Transition name may contain spaces + StringBuilder builder = new StringBuilder(command[2]); + int i = 3; + while(i params) + { + // build a complete anonymous type for the start task + List aspects = typeDef.getDefaultAspects(); + List aspectNames = new ArrayList(aspects.size()); + getMandatoryAspects(typeDef, aspectNames); + ClassDefinition startTaskDef = dictionaryService.getAnonymousType(typeDef.getName(), aspectNames); + + // apply default values + Map propertyDefs = startTaskDef.getProperties(); + for (Map.Entry entry : propertyDefs.entrySet()) + { + String defaultValue = entry.getValue().getDefaultValue(); + + if (params.get(entry.getKey()) == null) + { + if (defaultValue != null) + { + params.put(entry.getKey(), (Serializable)DefaultTypeConverter.INSTANCE.convert(entry.getValue().getDataType(), defaultValue)); + } + } + else + { + params.put(entry.getKey(), (Serializable)DefaultTypeConverter.INSTANCE.convert(entry.getValue().getDataType(), params.get(entry.getKey()))); + } + } + + if (params.containsKey(WorkflowModel.ASSOC_ASSIGNEE)) + { + String value = (String)params.get(WorkflowModel.ASSOC_ASSIGNEE); + ArrayList assignees = new ArrayList(); + assignees.add(personService.getPerson(value)); + params.put(WorkflowModel.ASSOC_ASSIGNEE, assignees); + } + + params.put(WorkflowModel.ASSOC_PACKAGE, workflowService.createPackage(null)); + } + + private void getMandatoryAspects(ClassDefinition classDef, List aspects) + { + for (AspectDefinition aspect : classDef.getDefaultAspects()) + { + QName aspectName = aspect.getName(); + if (!aspects.contains(aspectName)) + { + aspects.add(aspect.getName()); + getMandatoryAspects(aspect, aspects); + } + } + } + + private void endStartTaskForPath(WorkflowPath path) + { + if (path != null) + { + List tasks = this.workflowService.getTasksForWorkflowPath(path.id); + if (tasks.size() == 1) + { + WorkflowTask startTask = tasks.get(0); + + if (startTask.state == WorkflowTaskState.IN_PROGRESS) + { + // end the start task to trigger the first 'proper' + // task in the workflow + this.workflowService.endTask(startTask.id, null); + } + } + } + } } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java b/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java index 6cc4177523..22e164c237 100644 --- a/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java +++ b/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java @@ -25,6 +25,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; @@ -552,14 +554,54 @@ public class WorkflowServiceImpl implements WorkflowService */ public WorkflowInstance cancelWorkflow(String workflowId) { - String engineId = BPMEngineRegistry.getEngineId(workflowId); - WorkflowComponent component = getWorkflowComponent(engineId); - WorkflowInstance instance = component.cancelWorkflow(workflowId); - // NOTE: Delete workflow package after cancelling workflow, so it's - // still available - // in process-end events of workflow definition - workflowPackageComponent.deletePackage(instance.getWorkflowPackage()); - return instance; + return cancelWorkflows(Collections.singletonList(workflowId)).get(0); + } + + /* + * (non-Javadoc) + * @see + * org.alfresco.service.cmr.workflow.WorkflowService#cancelWorkflows + */ + public List cancelWorkflows(List workflowIds) + { + List result = new ArrayList(workflowIds.size()); + + // Batch the workflow IDs by engine ID + Map> workflows = batchByEngineId(workflowIds); + + // Process each engine's batch + for (Map.Entry> entry : workflows.entrySet()) + { + String engineId = entry.getKey(); + WorkflowComponent component = getWorkflowComponent(engineId); + List instances = component.cancelWorkflows(entry.getValue()); + for (WorkflowInstance instance : instances) + { + // NOTE: Delete workflow package after cancelling workflow, so it's + // still available + // in process-end events of workflow definition + workflowPackageComponent.deletePackage(instance.getWorkflowPackage()); + result.add(instance); + } + } + return result; + } + + private Map> batchByEngineId(List workflowIds) + { + Map> workflows = new LinkedHashMap>(workflowIds.size() * 2); + for (String workflowId: workflowIds) + { + String engineId = BPMEngineRegistry.getEngineId(workflowId); + List engineWorkflows = workflows.get(engineId); + if (engineWorkflows == null) + { + engineWorkflows = new LinkedList(); + workflows.put(engineId, engineWorkflows); + } + engineWorkflows.add(workflowId); + } + return workflows; } /* @@ -644,6 +686,33 @@ public class WorkflowServiceImpl implements WorkflowService return component.getStartTask(workflowInstanceId); } + @Override + public List getStartTasks(List workflowInstanceIds, boolean sameSession) + { + List result = new ArrayList(workflowInstanceIds.size()); + + // Batch the workflow IDs by engine ID + Map> workflows = batchByEngineId(workflowInstanceIds); + + // Process each engine's batch + for (Map.Entry> entry : workflows.entrySet()) + { + String engineId = entry.getKey(); + TaskComponent component = getTaskComponent(engineId); + List startTasks = component.getStartTasks(entry.getValue(), sameSession); + + // Optimization to allow 'lazy list' to pass through + if (workflows.size() == 1) + { + return startTasks; + } + + result.addAll(startTasks); + } + + return result; + } + /* * (non-Javadoc) * @see @@ -722,6 +791,11 @@ public class WorkflowServiceImpl implements WorkflowService * .service.cmr.workflow.WorkflowTaskFilter) */ public List queryTasks(WorkflowTaskQuery query) + { + return queryTasks(query, false); + } + + public List queryTasks(WorkflowTaskQuery query, boolean sameSession) { // extract task component to perform query String engineId = null; @@ -740,8 +814,9 @@ public class WorkflowServiceImpl implements WorkflowService } // perform query - List tasks = new ArrayList(10); + List tasks; String[] ids = registry.getTaskComponents(); + List taskComponents = new ArrayList(ids.length); for (String id : ids) { TaskComponent component = registry.getTaskComponent(id); @@ -752,7 +827,19 @@ public class WorkflowServiceImpl implements WorkflowService { continue; } - tasks.addAll(component.queryTasks(query)); + taskComponents.add(component); + } + if (taskComponents.size() == 1) + { + tasks = taskComponents.get(0).queryTasks(query, sameSession); + } + else + { + tasks = new ArrayList(10); + for (TaskComponent component: taskComponents) + { + tasks.addAll(component.queryTasks(query, sameSession)); + } } return Collections.unmodifiableList(tasks); } diff --git a/source/java/org/alfresco/repo/workflow/activiti/ActivitiWorkflowEngine.java b/source/java/org/alfresco/repo/workflow/activiti/ActivitiWorkflowEngine.java index 50e323238a..7952f485ef 100644 --- a/source/java/org/alfresco/repo/workflow/activiti/ActivitiWorkflowEngine.java +++ b/source/java/org/alfresco/repo/workflow/activiti/ActivitiWorkflowEngine.java @@ -220,7 +220,22 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine this.historyService = activitiUtil.getHistoryService(); this.managementService = activitiUtil.getManagementService(); } + + /* (non-Javadoc) + * @see org.alfresco.repo.workflow.WorkflowComponent#cancelWorkflows(java.util.List) + */ + @Override + public List cancelWorkflows(List workflowIds) + { + List result = new ArrayList(workflowIds.size()); + for (String workflowId : workflowIds) + { + result.add(cancelWorkflow(workflowId)); + } + return result; + } + /** * {@inheritDoc} */ @@ -1544,6 +1559,16 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine } } + + /* (non-Javadoc) + * @see org.alfresco.repo.workflow.TaskComponent#queryTasks(org.alfresco.service.cmr.workflow.WorkflowTaskQuery, boolean) + */ + @Override + public List queryTasks(WorkflowTaskQuery query, boolean sameSession) + { + return queryTasks(query); + } + /** * {@inheritDoc} */ @@ -2047,7 +2072,26 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine } - /** + + /* (non-Javadoc) + * @see org.alfresco.repo.workflow.TaskComponent#getStartTasks(java.util.List, boolean) + */ + @Override + public List getStartTasks(List workflowInstanceIds, boolean sameSession) + { + List result = new ArrayList(workflowInstanceIds.size()); + for (String workflowInstanceId : workflowInstanceIds) + { + WorkflowTask startTask = getStartTask(workflowInstanceId); + if (startTask != null) + { + result.add(startTask); + } + } + return result; + } + + /** * {@inheritDoc} */ public WorkflowTask getStartTask(String workflowInstanceId) diff --git a/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java b/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java index 5ef1f54505..a964ae1a92 100644 --- a/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java +++ b/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java @@ -1,3454 +1,3636 @@ -/* - * 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.workflow.jbpm; - -import java.io.IOException; -import java.io.InputStream; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.zip.ZipInputStream; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.content.MimetypeMap; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.security.authority.AuthorityDAO; -import org.alfresco.repo.workflow.AlfrescoBpmEngine; -import org.alfresco.repo.workflow.WorkflowConstants; -import org.alfresco.repo.workflow.WorkflowEngine; -import org.alfresco.repo.workflow.WorkflowModel; -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.dictionary.AssociationDefinition; -import org.alfresco.service.cmr.dictionary.ClassDefinition; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.service.cmr.dictionary.PropertyDefinition; -import org.alfresco.service.cmr.dictionary.TypeDefinition; -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.cmr.repository.datatype.DefaultTypeConverter; -import org.alfresco.service.cmr.search.SearchService; -import org.alfresco.service.cmr.security.AuthorityType; -import org.alfresco.service.cmr.security.PersonService; -import org.alfresco.service.cmr.workflow.WorkflowDefinition; -import org.alfresco.service.cmr.workflow.WorkflowDeployment; -import org.alfresco.service.cmr.workflow.WorkflowException; -import org.alfresco.service.cmr.workflow.WorkflowInstance; -import org.alfresco.service.cmr.workflow.WorkflowNode; -import org.alfresco.service.cmr.workflow.WorkflowPath; -import org.alfresco.service.cmr.workflow.WorkflowTask; -import org.alfresco.service.cmr.workflow.WorkflowTaskDefinition; -import org.alfresco.service.cmr.workflow.WorkflowTaskQuery; -import org.alfresco.service.cmr.workflow.WorkflowTaskState; -import org.alfresco.service.cmr.workflow.WorkflowTimer; -import org.alfresco.service.cmr.workflow.WorkflowTransition; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; -import org.alfresco.util.GUID; -import org.alfresco.util.collections.CollectionUtils; -import org.alfresco.util.collections.Function; -import org.hibernate.CacheMode; -import org.hibernate.Criteria; -import org.hibernate.FlushMode; -import org.hibernate.Query; -import org.hibernate.Session; -import org.hibernate.criterion.Conjunction; -import org.hibernate.criterion.Disjunction; -import org.hibernate.criterion.Order; -import org.hibernate.criterion.Projections; -import org.hibernate.criterion.Property; -import org.hibernate.criterion.Restrictions; -import org.hibernate.proxy.HibernateProxy; -import org.jbpm.JbpmContext; -import org.jbpm.JbpmException; -import org.jbpm.context.exe.ContextInstance; -import org.jbpm.context.exe.TokenVariableMap; -import org.jbpm.context.exe.VariableInstance; -import org.jbpm.context.exe.converter.BooleanToStringConverter; -import org.jbpm.db.GraphSession; -import org.jbpm.db.TaskMgmtSession; -import org.jbpm.file.def.FileDefinition; -import org.jbpm.graph.def.Event; -import org.jbpm.graph.def.Node; -import org.jbpm.graph.def.ProcessDefinition; -import org.jbpm.graph.def.Transition; -import org.jbpm.graph.exe.Comment; -import org.jbpm.graph.exe.ExecutionContext; -import org.jbpm.graph.exe.ProcessInstance; -import org.jbpm.graph.exe.Token; -import org.jbpm.job.Timer; -import org.jbpm.jpdl.par.ProcessArchive; -import org.jbpm.jpdl.xml.Problem; -import org.jbpm.taskmgmt.def.Task; -import org.jbpm.taskmgmt.def.TaskMgmtDefinition; -import org.jbpm.taskmgmt.exe.PooledActor; -import org.jbpm.taskmgmt.exe.TaskInstance; -import org.springframework.dao.DataAccessException; -import org.springframework.util.StringUtils; -import org.springmodules.workflow.jbpm31.JbpmCallback; -import org.springmodules.workflow.jbpm31.JbpmTemplate; - - -/** - * JBoss JBPM based implementation of: - * - * Workflow Definition Component Workflow Component Task Component - * - * @author davidc - */ -public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine -{ - // Implementation dependencies - protected NodeService nodeService; - protected ServiceRegistry serviceRegistry; - protected PersonService personService; - protected AuthorityDAO authorityDAO; - protected JbpmTemplate jbpmTemplate; - protected SearchService unprotectedSearchService; - - // Company Home - protected StoreRef companyHomeStore; - protected String companyHomePath; - - // Note: jBPM query which is not provided out-of-the-box - // TODO: Check jBPM 3.2 and get this implemented in jBPM - private final static String COMPLETED_TASKS_QUERY = "select ti " + "from org.jbpm.taskmgmt.exe.TaskInstance as ti " - + "where ti.actorId = :actorId " + "and ti.isOpen = false " + "and ti.end is not null"; - - // Note: jBPM query which is not provided out-of-the-box - // TODO: Check jBPMg future and get this implemented in jBPM - private final static String PROCESS_TIMERS_QUERY = "select timer " + "from org.jbpm.job.Timer timer " - + "where timer.processInstance = :process "; - - // Workflow Path Seperators - private final static String WORKFLOW_PATH_SEPERATOR = "-"; - private final static String WORKFLOW_TOKEN_SEPERATOR = "@"; - - // I18N labels - private final static String TITLE_LABEL = "title"; - private final static String DESC_LABEL = "description"; - private final static String DEFAULT_TRANSITION_LABEL = "bpm_businessprocessmodel.transition"; - - private static final String ERR_MANDATORY_TASK_PROPERTIES_MISSING = "Jbpm.engine.mandatory.properties.missing"; - private static final String ERR_DEPLOY_WORKFLOW= "jbpm.engine.deploy.workflow.error"; - private static final String ERR_IS_WORKFLOW_DEPLOYED = "jbpm.engine.is.workflow.deployed.error"; - private static final String ERR_UNDEPLOY_WORKFLOW = "jbpm.engine.undeploy.workflow.error"; - private static final String ERR_GET_WORKFLOW_DEF = "jbpm.engine.get.workflow.definition.error"; - private static final String ERR_GET_WORKFLOW_DEF_BY_ID = "jbpm.engine.get.workflow.definition.by.id.error"; - private static final String ERR_GET_WORKFLOW_DEF_BY_NAME = "jbpm.engine.get.workflow.definition.by.name.error"; - private static final String ERR_GET_ALL_DEFS_BY_NAME = "jbpm.engine.get.all.workflow.definitions.by.name.error"; - private static final String ERR_GET_DEF_IMAGE = "jbpm.engine.get.workflow.definition.image.error"; - private static final String ERR_GET_TASK_DEFS = "jbpm.engine.get.task.definitions.error"; - private static final String ERR_GET_PROCESS_DEF = "jbpm.engine.get.process.definition.error"; - private static final String ERR_START_WORKFLOW = "jbpm.enginestart.workflow.error"; - private static final String ERR_GET_ACTIVE_WORKFLOW_INSTS = "jbpm.engine.get.active.workflows.error"; - private static final String ERR_GET_WORKFLOW_INST_BY_ID = "jbpm.engine.get.workflow.instance.by.id.error"; - private static final String ERR_GET_PROCESS_INSTANCE = "jbpm.engine.get.process.instance.error"; - private static final String ERR_GET_WORKFLOW_PATHS = "jbpm.engine.get.workflow.paths.error"; - private static final String ERR_GET_PATH_PROPERTIES = "jbpm.engine.get.path.properties.error"; - private static final String ERR_CANCEL_WORKFLOW = "jbpm.engine.cancel.workflow.error"; - private static final String ERR_DELETE_WORKFLOW = "jbpm.engine.delete.workflow.error"; - private static final String ERR_SIGNAL_TRANSITION = "jbpm.engine.signal.transition.error"; - protected static final String ERR_INVALID_EVENT = "jbpm.engine.invalid.event"; - private static final String ERR_FIRE_EVENT = "jbpm.engine.fire.event.error"; - private static final String ERR_GET_TASKS_FOR_PATH = "jbpm.engine.get.tasks.for.path.error"; - private static final String ERR_GET_TIMERS = "jbpm.engine.get.timers.error"; - protected static final String ERR_FIND_COMPLETED_TASK_INSTS = "jbpm.engine.find.completed.task.instances.error"; - private static final String ERR_GET_ASSIGNED_TASKS = "jbpm.engine.get.assigned.tasks.error"; - private static final String ERR_GET_POOLED_TASKS = "jbpm.engine.get.pooled.tasks.error"; - private static final String ERR_QUERY_TASKS = "jbpm.engine.query.tasks.error"; - private static final String ERR_GET_TASK_INST = "jbpm.engine.get.task.instance.error"; - private static final String ERR_UPDATE_TASK = "jbpm.engine.update.task.error"; - protected static final String ERR_END_TASK_INVALID_TRANSITION ="jbpm.engine.end.task.invalid.transition"; - private static final String ERR_END_TASK = "jbpm.engine.end.task.error"; - private static final String ERR_GET_TASK_BY_ID = "jbpm.engine.get.task.by.id.error"; - private static final String ERR_GET_START_TASK = "jbpm.engine.get.start.task.error"; - private static final String ERR_COMPILE_PROCESS_DEF_zip = "jbpm.engine.compile.process.definition.zip.error"; - private static final String ERR_COMPILE_PROCESS_DEF_XML = "jbpm.engine.compile.process.definition.xml.error"; - private static final String ERR_COMPILE_PROCESS_DEF_UNSUPPORTED = "jbpm.engine.compile.process.definition.unsupported.error"; - private static final String ERR_GET_JBPM_ID = "jbpm.engine.get.jbpm.id.error"; - private static final String ERR_GET_WORKFLOW_TOKEN_INVALID = "jbpm.engine.get.workflow.token.invalid"; - private static final String ERR_GET_WORKFLOW_TOKEN_NULL = "jbpm.engine.get.workflow.token.is.null"; - private static final String ERR_SET_TASK_PROPS_INVALID_VALUE = "jbpm.engine.set.task.properties.invalid.value"; - private static final String ERR_CONVERT_VALUE = "jbpm.engine.convert.value.error"; - private static final String ERR_GET_COMPANY_HOME_INVALID = "jbpm.engine.get.company.home.invalid"; - private static final String ERR_GET_COMPANY_HOME_MULTIPLE = "jbpm.engine.get.company.home.multiple"; - - // engine ID - public static final String ENGINE_ID = "jbpm"; - - /** - * Sets the JBPM Template used for accessing JBoss JBPM in the correct - * context - * - * @param jbpmTemplate - */ - public void setJBPMTemplate(JbpmTemplate jbpmTemplate) - { - this.jbpmTemplate = jbpmTemplate; - } - - /** - * Sets the Node Service - * - * @param nodeService - */ - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - /** - * Sets the Person Service - * - * @param personService - */ - public void setPersonService(PersonService personService) - { - this.personService = personService; - } - - /** - * Sets the Authority DAO - * - * @param authorityDAO - */ - public void setAuthorityDAO(AuthorityDAO authorityDAO) - { - this.authorityDAO = authorityDAO; - } - - /** - * Sets the Service Registry - * - * @param serviceRegistry - */ - public void setServiceRegistry(ServiceRegistry serviceRegistry) - { - this.serviceRegistry = serviceRegistry; - } - - /** - * Sets the Company Home Path - * - * @param companyHomePath - */ - public void setCompanyHomePath(String companyHomePath) - { - this.companyHomePath = companyHomePath; - } - - /** - * Sets the Company Home Store - * - * @param companyHomeStore - */ - public void setCompanyHomeStore(String companyHomeStore) - { - this.companyHomeStore = new StoreRef(companyHomeStore); - } - - /** - * Set the unprotected search service - so we can find the node ref for - * company home when folk do not have read access to company home TODO: - * review use with DC - * - * @param unprotectedSearchService - */ - public void setUnprotectedSearchService(SearchService unprotectedSearchService) - { - this.unprotectedSearchService = unprotectedSearchService; - } - - // - // Workflow Definition... - // - - /* - * @see org.alfresco.repo.workflow.WorkflowComponent#deployDefinition(java.io.InputStream, java.lang.String) - */ - public WorkflowDeployment deployDefinition(final InputStream workflowDefinition, final String mimetype) - { - return deployDefinition(workflowDefinition, mimetype, null); - } - - /* - * @see org.alfresco.repo.workflow.WorkflowComponent#deployDefinition(java.io.InputStream, java.lang.String, java.lang.String) - */ - public WorkflowDeployment deployDefinition(final InputStream workflowDefinition, final String mimetype, final String name) - { - try - { - return (WorkflowDeployment)jbpmTemplate.execute(new JbpmCallback() - { - public Object doInJbpm(JbpmContext context) - { - // construct process definition - CompiledProcessDefinition compiledDef = compileProcessDefinition(workflowDefinition, mimetype); - - // deploy the parsed definition - context.deployProcessDefinition(compiledDef.def); - - // return deployed definition - WorkflowDeployment workflowDeployment = createWorkflowDeployment(compiledDef); - return workflowDeployment; - } - }); - } - catch(JbpmException e) - { - String msg = messageService.getMessage(ERR_DEPLOY_WORKFLOW); - throw new WorkflowException(msg, e); - } - } - - /* - * (non-Javadoc) - * - * @see - * org.alfresco.repo.workflow.WorkflowDefinitionComponent#isDefinitionDeployed - * (java.io.InputStream, java.lang.String) - */ - public boolean isDefinitionDeployed(final InputStream workflowDefinition, final String mimetype) - { - try - { - return (Boolean) jbpmTemplate.execute(new JbpmCallback() - { - public Boolean doInJbpm(JbpmContext context) - { - // create process definition from input stream - CompiledProcessDefinition processDefinition = compileProcessDefinition(workflowDefinition, mimetype); - - // retrieve process definition from Alfresco Repository - GraphSession graphSession = context.getGraphSession(); - String definitionName = processDefinition.def.getName(); - ProcessDefinition existingDefinition = graphSession.findLatestProcessDefinition(definitionName); - return existingDefinition != null; - } - }); - } - catch(JbpmException e) - { - String msg = messageService.getMessage(ERR_IS_WORKFLOW_DEPLOYED); - throw new WorkflowException(msg, e); - } - } - - /* - * (non-Javadoc) - * - * @see - * org.alfresco.repo.workflow.WorkflowDefinitionComponent#undeployDefinition - * (java.lang.String) - */ - public void undeployDefinition(final String workflowDefinitionId) - { - try - { - jbpmTemplate.execute(new JbpmCallback() - { - public Object doInJbpm(JbpmContext context) - { - // retrieve process definition - GraphSession graphSession = context.getGraphSession(); - ProcessDefinition processDefinition = getProcessDefinition(graphSession, workflowDefinitionId); - - // undeploy - // NOTE: jBPM deletes all "in-flight" processes too - // TODO: Determine if there's a safer undeploy we can expose - // via the WorkflowService contract - graphSession.deleteProcessDefinition(processDefinition); - - // we're done - return null; - } - }); - } - catch(JbpmException e) - { - String msg = messageService.getMessage(ERR_UNDEPLOY_WORKFLOW, workflowDefinitionId); - throw new WorkflowException(msg, e); - } - } - - /** - * {@inheritDoc} - */ - @SuppressWarnings("unchecked") - public List getDefinitions() - { - try - { - return (List) jbpmTemplate.execute(new JbpmCallback() - { - public Object doInJbpm(JbpmContext context) - { - GraphSession graphSession = context.getGraphSession(); - List processDefs = graphSession.findLatestProcessDefinitions(); - return getValidDefinitions(processDefs); - } - }); - } - catch (JbpmException e) - { - String msg = messageService.getMessage(ERR_GET_WORKFLOW_DEF); - throw new WorkflowException(msg, e); - } - } - - private List getValidDefinitions(Collection definitions) - { - List filteredDefs = factory.filterByDomain(definitions, new Function() - { - public String apply(ProcessDefinition definition) - { - return definition.getName(); - } - }); - return convertDefinitions(filteredDefs); - } - - private List convertDefinitions(Collection definitions) - { - return CollectionUtils.transform(definitions, new Function() - { - public WorkflowDefinition apply(ProcessDefinition value) - { - return createWorkflowDefinition(value); - } - }); - } - - private List convertWorkflows(Collection instances) - { - return CollectionUtils.transform(instances, new Function() - { - public WorkflowInstance apply(ProcessInstance value) - { - return createWorkflowInstance(value); - } - }); - } - - /* - * (non-Javadoc) - * - * @see - * org.alfresco.repo.workflow.WorkflowDefinitionComponent#getDefinitions() - */ - @SuppressWarnings("unchecked") - public List getAllDefinitions() - { - try - { - return (List) jbpmTemplate.execute(new JbpmCallback() - { - public Object doInJbpm(JbpmContext context) - { - GraphSession graphSession = context.getGraphSession(); - List processDefs = graphSession.findAllProcessDefinitions(); - return getValidDefinitions(processDefs); - } - }); - } - catch (JbpmException e) - { - String msg = messageService.getMessage(ERR_GET_WORKFLOW_DEF); - throw new WorkflowException(msg, e); - } - } - - /* - * (non-Javadoc) - * - * @see - * org.alfresco.repo.workflow.WorkflowDefinitionComponent#getDefinitionById - * (java.lang.String) - */ - public WorkflowDefinition getDefinitionById(final String workflowDefinitionId) - { - try - { - return (WorkflowDefinition)jbpmTemplate.execute(new JbpmCallback() - { - public Object doInJbpm(JbpmContext context) - { - // retrieve process - GraphSession graphSession = context.getGraphSession(); - ProcessDefinition processDefinition = getProcessDefinition(graphSession, workflowDefinitionId); - return createWorkflowDefinition(processDefinition); - } - }); - } - catch(JbpmException e) - { - String msg = messageService.getMessage(ERR_GET_WORKFLOW_DEF_BY_ID, workflowDefinitionId); - throw new WorkflowException(msg, e); - } - } - - /* - * (non-Javadoc) - * - * @see - * org.alfresco.repo.workflow.WorkflowComponent#getDefinitionByName(java - * .lang.String) - */ - public WorkflowDefinition getDefinitionByName(final String workflowName) - { - try - { - return (WorkflowDefinition)jbpmTemplate.execute(new JbpmCallback() - { - public Object doInJbpm(JbpmContext context) - { - GraphSession graphSession = context.getGraphSession(); - String definitionName = tenantService.getName(createLocalId(workflowName)); - ProcessDefinition processDef = graphSession.findLatestProcessDefinition(definitionName); - return processDef == null ? null : createWorkflowDefinition(processDef); - } - }); - } - catch(JbpmException e) - { - String msg = messageService.getMessage(ERR_GET_WORKFLOW_DEF_BY_NAME, workflowName); - throw new WorkflowException(msg, e); - } - } - - /* - * (non-Javadoc) - * - * @see - * org.alfresco.repo.workflow.WorkflowComponent#getAllDefinitionsByName( - * java.lang.String) - */ - @SuppressWarnings("unchecked") - public List getAllDefinitionsByName(final String workflowName) - { - try - { - return (List)jbpmTemplate.execute(new JbpmCallback() - { - @SuppressWarnings("synthetic-access") - public Object doInJbpm(JbpmContext context) - { - GraphSession graphSession = context.getGraphSession(); - String definitionName = tenantService.getName(createLocalId(workflowName)); - List processDefs = graphSession.findAllProcessDefinitionVersions(definitionName); - return convertDefinitions(processDefs); - } - }); - } - catch(JbpmException e) - { - String msg = messageService.getMessage(ERR_GET_ALL_DEFS_BY_NAME, workflowName); - throw new WorkflowException(msg, e); - } - } - - /* - * (non-Javadoc) - * - * @see - * org.alfresco.repo.workflow.WorkflowComponent#getDefinitionImage(java. - * lang.String) - */ - public byte[] getDefinitionImage(final String workflowDefinitionId) - { - try - { - return (byte[])jbpmTemplate.execute(new JbpmCallback() - { - @SuppressWarnings("synthetic-access") - public Object doInJbpm(JbpmContext context) - { - GraphSession graphSession = context.getGraphSession(); - ProcessDefinition processDefinition = getProcessDefinition(graphSession, workflowDefinitionId); - FileDefinition fileDefinition = processDefinition.getFileDefinition(); - return (fileDefinition == null) ? null : fileDefinition.getBytes("processimage.jpg"); - } - }); - } - catch(JbpmException e) - { - String msg = messageService.getMessage(ERR_GET_DEF_IMAGE, workflowDefinitionId); - throw new WorkflowException(msg, e); - } - } - - /* - * (non-Javadoc) - * - * @see - * org.alfresco.repo.workflow.WorkflowComponent#getAllTaskDefinitions(java - * .lang.String) - */ - @SuppressWarnings("unchecked") - public List getTaskDefinitions(final String workflowDefinitionId) - { - try - { - return (List)jbpmTemplate.execute(new JbpmCallback() - { - public Object doInJbpm(JbpmContext context) - { - // retrieve process - GraphSession graphSession = context.getGraphSession(); - ProcessDefinition processDefinition = getProcessDefinition(graphSession, workflowDefinitionId); - - if (processDefinition == null) - { - return null; - } - else - { - String processName = processDefinition.getName(); - if (tenantService.isEnabled()) - { - tenantService.checkDomain(processName); // throws - // exception - // if domain - // mismatch - } - - TaskMgmtDefinition taskMgmtDef = processDefinition.getTaskMgmtDefinition(); - List workflowTaskDefs = new ArrayList(); - for (Object task : taskMgmtDef.getTasks().values()) - { - workflowTaskDefs.add(createWorkflowTaskDefinition((Task)task)); - } - return (workflowTaskDefs.size() == 0) ? null : workflowTaskDefs; - } - } - }); - } - catch(JbpmException e) - { - String msg = messageService.getMessage(ERR_GET_TASK_DEFS, workflowDefinitionId); - throw new WorkflowException(msg, e); - } - } - - /** - * Gets a jBPM process definition - * - * @param graphSession - * jBPM graph session - * @param workflowDefinitionId - * workflow definition id - * @return process definition - */ - protected ProcessDefinition getProcessDefinition(GraphSession graphSession, String workflowDefinitionId) - { - ProcessDefinition processDefinition = graphSession.getProcessDefinition(getJbpmId(workflowDefinitionId)); - - if ((processDefinition != null) && (tenantService.isEnabled())) - { - try - { - tenantService.checkDomain(processDefinition.getName()); // throws - // exception - // if - // domain - // mismatch - } - catch (RuntimeException re) - { - processDefinition = null; - } - } - - if (processDefinition == null) - { - String msg = messageService.getMessage(ERR_GET_PROCESS_DEF, workflowDefinitionId); - throw new WorkflowException(msg); - } - return processDefinition; - } - - - // - // Workflow Instance Management... - // - - /* - * (non-Javadoc) - * - * @see - * org.alfresco.repo.workflow.WorkflowComponent#startWorkflow(java.lang. - * String, java.util.Map) - */ - public WorkflowPath startWorkflow(final String workflowDefinitionId, final Map parameters) - { - try - { - return (WorkflowPath) jbpmTemplate.execute(new JbpmCallback() - { - @SuppressWarnings("synthetic-access") - public Object doInJbpm(JbpmContext context) - { - // initialise jBPM actor (for any processes that wish to - // record the initiator) - String currentUserName = AuthenticationUtil.getFullyAuthenticatedUser(); - context.setActorId(currentUserName); - - // construct a new process - GraphSession graphSession = context.getGraphSession(); - ProcessDefinition processDefinition = getProcessDefinition(graphSession, workflowDefinitionId); - ProcessInstance processInstance = new ProcessInstance(processDefinition); - processInstance.setKey(GUID.generate()); - - // assign initial process context - ContextInstance processContext = processInstance.getContextInstance(); - processContext.setVariable(WorkflowConstants.PROP_CANCELLED, false); - if(parameters!=null) - { - Serializable packageNode = parameters.get(WorkflowModel.ASSOC_PACKAGE); - if (packageNode != null) - { - String pckgName = factory.mapQNameToName(WorkflowModel.ASSOC_PACKAGE); - processContext.setVariable(pckgName, new JBPMNode((NodeRef) packageNode, serviceRegistry)); - } - } - NodeRef companyHome = getCompanyHome(); - processContext.setVariable(WorkflowConstants.PROP_COMPANY_HOME, new JBPMNode(companyHome, serviceRegistry)); - NodeRef initiatorPerson = mapNameToPerson(currentUserName); - if (initiatorPerson != null) - { - processContext.setVariable(WorkflowConstants.PROP_INITIATOR, new JBPMNode(initiatorPerson, serviceRegistry)); - NodeRef initiatorHome = (NodeRef) nodeService.getProperty(initiatorPerson, - ContentModel.PROP_HOMEFOLDER); - if (initiatorHome != null) - { - processContext.setVariable(WorkflowConstants.PROP_INITIATOR_HOME, new JBPMNode(initiatorHome, serviceRegistry)); - } - } - processContext.setVariable(WorkflowConstants.PROP_WORKFLOW_INSTANCE_ID, createGlobalId(new Long(processInstance.getId()) - .toString())); - - // create the start task if one exists - Token token = processInstance.getRootToken(); - Task startTask = processInstance.getTaskMgmtInstance().getTaskMgmtDefinition().getStartTask(); - if (startTask != null) - { - TaskInstance taskInstance = processInstance.getTaskMgmtInstance().createStartTaskInstance(); - setTaskProperties(taskInstance, parameters); - token = taskInstance.getToken(); - } - - // Save the process instance along with the task instance - context.save(processInstance); - return createWorkflowPath(token); - } - }); - } - catch(JbpmException e) - { - throw getStartWorkflowException(workflowDefinitionId, e); - } - catch (DataAccessException e) - { - throw getStartWorkflowException(workflowDefinitionId, e); - } - } - - private WorkflowException getStartWorkflowException(final String workflowDefinitionId, Exception e) - { - String msg = messageService.getMessage(ERR_START_WORKFLOW, workflowDefinitionId); - return new WorkflowException(msg, e); - } - - /** - * {@inheritDoc} - */ - public List getActiveWorkflows() - { - return getWorkflowsInternal(null, true); - } - - /** - * {@inheritDoc} - */ - @Override - public List getCompletedWorkflows() - { - return getWorkflowsInternal(null, false); - } - - /** - * {@inheritDoc} - */ - @Override - public List getWorkflows() - { - return getWorkflowsInternal(null, null); - } - - /** - * {@inheritDoc} - */ - public List getActiveWorkflows(final String workflowDefinitionId) - { - return getWorkflowsInternal(workflowDefinitionId, true); - } - - /** - * {@inheritDoc} - */ - public List getCompletedWorkflows(final String workflowDefinitionId) - { - return getWorkflowsInternal(workflowDefinitionId, false); - } - - /** - * {@inheritDoc} - */ - public List getWorkflows(final String workflowDefinitionId) - { - return getWorkflowsInternal(workflowDefinitionId, null); - } - - private List getWorkflowsInternal(String workflowDefinitionId, Boolean active) - { - try - { - final Long processDefId = workflowDefinitionId == null ? null : getJbpmId(workflowDefinitionId); - List instances = getProcessInstances(processDefId, active); - return convertWorkflows(instances); - } - catch(JbpmException e) - { - String msg = messageService.getMessage(ERR_GET_ACTIVE_WORKFLOW_INSTS, workflowDefinitionId); - throw new WorkflowException(msg, e); - } - } - - @SuppressWarnings("unchecked") - private List getProcessInstances(final Long processDefId, final Boolean active) - { - return (List) jbpmTemplate.execute(new JbpmCallback() - { - public Object doInJbpm(JbpmContext context) - { - Session session = context.getSession(); - Criteria criteria = session.createCriteria(ProcessInstance.class); - if(processDefId!=null) - { - Criteria definitionCriteria = criteria.createCriteria("processDefinition"); - definitionCriteria.add(Restrictions.eq("id", processDefId)); - } - if(Boolean.TRUE.equals(active)) - { - criteria.add(Restrictions.isNull("end")); - } - else if(Boolean.FALSE.equals(active)) - { - criteria.add(Restrictions.isNotNull("end")); - } - return criteria.list(); - } - }); - } - - /** - * {@inheritDoc} - */ - public WorkflowInstance getWorkflowById(final String workflowId) - { - try - { - return (WorkflowInstance) jbpmTemplate.execute(new JbpmCallback() - { - public Object doInJbpm(JbpmContext context) - { - // retrieve workflow - GraphSession graphSession = context.getGraphSession(); - ProcessInstance processInstance = getProcessInstanceIfExists(graphSession, workflowId); - return createWorkflowInstance(processInstance); - } - }); - } - catch(JbpmException e) - { - String msg = messageService.getMessage(ERR_GET_WORKFLOW_INST_BY_ID); - throw new WorkflowException(msg, e); - } - } - - private ProcessInstance getProcessInstanceIfExists(GraphSession graphSession, String workflowId) - { - ProcessInstance processInstance = graphSession.getProcessInstance(getJbpmId(workflowId)); - if ((processInstance != null) && (tenantService.isEnabled())) - { - try - { - tenantService.checkDomain(processInstance.getProcessDefinition().getName()); // throws - // exception - // if - // domain - // mismatch - } - catch (RuntimeException re) - { - processInstance = null; - } - } - return processInstance; - } - - /** - * Gets a jBPM Process Instance - * - * @param graphSession - * jBPM graph session - * @param workflowId - * workflow id - * @return process instance - */ - protected ProcessInstance getProcessInstance(GraphSession graphSession, String workflowId) - { - ProcessInstance processInstance = getProcessInstanceIfExists(graphSession, workflowId); - if (processInstance == null) - { - String msg = messageService.getMessage(ERR_GET_PROCESS_INSTANCE, workflowId); - throw new WorkflowException(msg); - } - return processInstance; - } - - /* - * (non-Javadoc) - * - * @see - * org.alfresco.repo.workflow.WorkflowComponent#getWorkflowPaths(java.lang - * .String) - */ - @SuppressWarnings("unchecked") - public List getWorkflowPaths(final String workflowId) - { - try - { - return (List) jbpmTemplate.execute(new JbpmCallback() - { - public Object doInJbpm(JbpmContext context) - { - // retrieve process instance - GraphSession graphSession = context.getGraphSession(); - ProcessInstance processInstance = getProcessInstance(graphSession, workflowId); - - // convert jBPM tokens to workflow posisitons - List tokens = processInstance.findAllTokens(); - List paths = new ArrayList(tokens.size()); - for (Token token : tokens) - { - if (!token.hasEnded()) - { - WorkflowPath path = createWorkflowPath(token); - paths.add(path); - } - } - - return paths; - } - }); - } - catch(JbpmException e) - { - String msg = messageService.getMessage(ERR_GET_WORKFLOW_PATHS, workflowId); - throw new WorkflowException(msg, e); - } - } - - /* - * (non-Javadoc) - * - * @see - * org.alfresco.repo.workflow.WorkflowComponent#getPathProperties(java.lang - * .String) - */ - @SuppressWarnings("unchecked") - public Map getPathProperties(final String pathId) - { - try - { - return (Map) jbpmTemplate.execute(new JbpmCallback() - { - public Map doInJbpm(JbpmContext context) - { - // retrieve jBPM token for workflow position - GraphSession graphSession = context.getGraphSession(); - Token token = getWorkflowToken(graphSession, pathId); - ContextInstance instanceContext = token.getProcessInstance().getContextInstance(); - Map properties = new HashMap(10); - while (token != null) - { - - TokenVariableMap varMap = instanceContext.getTokenVariableMap(token); - if (varMap != null) - { - Map tokenVars = varMap.getVariablesLocally(); - for (Map.Entry entry : tokenVars.entrySet()) - { - String key = entry.getKey(); - QName qname = factory.mapNameToQName(key); - - if (!properties.containsKey(key)) - { - Serializable value = convertValue(entry.getValue()); - properties.put(qname, value); - } - } - } - token = token.getParent(); - } - - return properties; - } - }); - } - catch(JbpmException e) - { - String msg = messageService.getMessage(ERR_GET_PATH_PROPERTIES, pathId); - throw new WorkflowException(msg, e); - } - } - - /* - * (non-Javadoc) - * - * @see - * org.alfresco.repo.workflow.WorkflowComponent#cancelWorkflow(java.lang - * .String) - */ - public WorkflowInstance cancelWorkflow(final String workflowId) - { - try - { - return (WorkflowInstance) jbpmTemplate.execute(new JbpmCallback() - { - public Object doInJbpm(JbpmContext context) - { - // retrieve and cancel process instance - GraphSession graphSession = context.getGraphSession(); - ProcessInstance processInstance = getProcessInstance(graphSession, workflowId); - processInstance.getContextInstance().setVariable("cancelled", true); - processInstance.end(); - // TODO: Determine if this is the most appropriate way to - // cancel workflow... - // It might be useful to record point at which it was - // cancelled etc - WorkflowInstance workflowInstance = createWorkflowInstance(processInstance); - - // delete the process instance - graphSession.deleteProcessInstance(processInstance, true, true); - return workflowInstance; - } - }); - } - catch(JbpmException e) - { - String msg = messageService.getMessage(ERR_CANCEL_WORKFLOW, workflowId); - throw new WorkflowException(msg, e); - } - } - - /* - * (non-Javadoc) - * - * @see - * org.alfresco.repo.workflow.WorkflowComponent#cancelWorkflow(java.lang - * .String) - */ - public WorkflowInstance deleteWorkflow(final String workflowId) - { - try - { - return (WorkflowInstance) jbpmTemplate.execute(new JbpmCallback() - { - public Object doInJbpm(JbpmContext context) - { - // retrieve and cancel process instance - GraphSession graphSession = context.getGraphSession(); - ProcessInstance processInstance = getProcessInstance(graphSession, workflowId); - - // delete the process instance - graphSession.deleteProcessInstance(processInstance, true, true); - Date endDate = new Date(); - WorkflowInstance workflowInstance = createWorkflowInstance(processInstance, endDate); - return workflowInstance; - } - }); - } - catch(JbpmException e) - { - String msg = messageService.getMessage(ERR_DELETE_WORKFLOW, workflowId); - throw new WorkflowException(msg, e); - } - } - - /* - * (non-Javadoc) - * - * @see - * org.alfresco.repo.workflow.WorkflowComponent#signal(java.lang.String, - * java.lang.String) - */ - public WorkflowPath signal(final String pathId, final String transition) - { - try - { - return (WorkflowPath) jbpmTemplate.execute(new JbpmCallback() - { - public Object doInJbpm(JbpmContext context) - { - // retrieve jBPM token for workflow position - GraphSession graphSession = context.getGraphSession(); - Token token = getWorkflowToken(graphSession, pathId); - - // signal the transition - if (transition == null) - { - token.signal(); - } - else - { - Node node = token.getNode(); - if (!node.hasLeavingTransition(transition)) - { - throw new WorkflowException("Transition '" + transition - + "' is invalid for Workflow path '" + pathId + "'"); - } - token.signal(transition); - } - - // save - ProcessInstance processInstance = token.getProcessInstance(); - context.save(processInstance); - - // return new workflow path - return createWorkflowPath(token); - } - }); - } - catch(JbpmException e) - { - String msg = messageService.getMessage(ERR_SIGNAL_TRANSITION, transition, pathId); - throw new WorkflowException(msg, e); - } - } - - /* - * (non-Javadoc) - * - * @see - * org.alfresco.repo.workflow.WorkflowComponent#fireEvent(java.lang.String, - * java.lang.String) - */ - public WorkflowPath fireEvent(final String pathId, final String event) - { - try - { - return (WorkflowPath) jbpmTemplate.execute(new JbpmCallback() - { - @SuppressWarnings("unchecked") - public Object doInJbpm(JbpmContext context) - { - // NOTE: Do not allow jBPM built-in events to be fired - if (event.equals(Event.EVENTTYPE_AFTER_SIGNAL) || event.equals(Event.EVENTTYPE_BEFORE_SIGNAL) - || event.equals(Event.EVENTTYPE_NODE_ENTER) || event.equals(Event.EVENTTYPE_NODE_LEAVE) - || event.equals(Event.EVENTTYPE_PROCESS_END) - || event.equals(Event.EVENTTYPE_PROCESS_START) - || event.equals(Event.EVENTTYPE_SUBPROCESS_CREATED) - || event.equals(Event.EVENTTYPE_SUBPROCESS_END) - || event.equals(Event.EVENTTYPE_SUPERSTATE_ENTER) - || event.equals(Event.EVENTTYPE_SUPERSTATE_LEAVE) - || event.equals(Event.EVENTTYPE_TASK_ASSIGN) - || event.equals(Event.EVENTTYPE_TASK_CREATE) || event.equals(Event.EVENTTYPE_TASK_END) - || event.equals(Event.EVENTTYPE_TASK_START) || event.equals(Event.EVENTTYPE_TIMER) - || event.equals(Event.EVENTTYPE_TRANSITION)) - { - String msg = messageService.getMessage(ERR_INVALID_EVENT, event); - throw new WorkflowException(msg); - } - - // retrieve jBPM token for workflow position - GraphSession graphSession = context.getGraphSession(); - Token token = getWorkflowToken(graphSession, pathId); - - ExecutionContext executionContext = new ExecutionContext(token); - TaskMgmtSession taskSession = context.getTaskMgmtSession(); - List tasks = taskSession.findTaskInstancesByToken(token.getId()); - if (tasks.size() == 0) - { - // fire the event against current node for the token - Node node = token.getNode(); - node.fireEvent(event, executionContext); - } - else - { - // fire the event against tasks associated with the node - // NOTE: this will also propagate the event to the node - for (TaskInstance task : tasks) - { - executionContext.setTaskInstance(task); - task.getTask().fireEvent(event, executionContext); - } - } - - // save - ProcessInstance processInstance = token.getProcessInstance(); - context.save(processInstance); - - // return new workflow path - return createWorkflowPath(token); - } - }); - } - catch(JbpmException e) - { - String msg = messageService.getMessage(ERR_FIRE_EVENT, event, pathId); - throw new WorkflowException(msg, e); - } - } - - /* - * (non-Javadoc) - * - * @see - * org.alfresco.repo.workflow.WorkflowComponent#getTasksForWorkflowPath( - * java.lang.String) - */ - @SuppressWarnings("unchecked") - public List getTasksForWorkflowPath(final String pathId) - { - try - { - return (List) jbpmTemplate.execute(new JbpmCallback() - { - public List doInJbpm(JbpmContext context) - { - // retrieve tasks at specified workflow path - GraphSession graphSession = context.getGraphSession(); - Token token = getWorkflowToken(graphSession, pathId); - TaskMgmtSession taskSession = context.getTaskMgmtSession(); - List tasks = taskSession.findTaskInstancesByToken(token.getId()); - return getWorkflowTasks(tasks); - } - }); - } - catch(JbpmException e) - { - String msg = messageService.getMessage(ERR_GET_TASKS_FOR_PATH, pathId); - throw new WorkflowException(msg, e); - } - } - - /* - * (non-Javadoc) - * - * @see - * org.alfresco.repo.workflow.WorkflowComponent#getTimers(java.lang.String) - */ - @SuppressWarnings("unchecked") - public List getTimers(final String workflowId) - { - try - { - return (List) jbpmTemplate.execute(new JbpmCallback() - { - public List doInJbpm(JbpmContext context) - { - // retrieve process - GraphSession graphSession = context.getGraphSession(); - ProcessInstance process = getProcessInstance(graphSession, workflowId); - - // retrieve timers for process - Session session = context.getSession(); - Query query = session.createQuery(PROCESS_TIMERS_QUERY); - query.setEntity("process", process); - List timers = query.list(); - - // convert timers to appropriate service response format - List workflowTimers = new ArrayList(timers.size()); - for (Timer timer : timers) - { - WorkflowTimer workflowTimer = createWorkflowTimer(timer); - workflowTimers.add(workflowTimer); - } - return workflowTimers; - } - }); - } - catch(JbpmException e) - { - String msg = messageService.getMessage(ERR_GET_TIMERS, workflowId); - throw new WorkflowException(msg, e); - } - } - - /* - * (non-Javadoc) - * - * @see org.alfresco.repo.workflow.WorkflowComponent#hasWorkflowImage(java.lang.String) - */ - public boolean hasWorkflowImage(final String workflowInstanceId) - { - // we don't support workflow instance diagrams in JBPM so return false - return false; - } - - /* - * (non-Javadoc) - * - * @see org.alfresco.repo.workflow.WorkflowComponent#getWorkflowImage(java.lang.String) - */ - public InputStream getWorkflowImage(final String workflowInstanceId) - { - // we don't support workflow instance diagrams in JBPM so return null - return null; - } - - // - // Task Management ... - // - - /* - * (non-Javadoc) - * - * @see - * org.alfresco.repo.workflow.TaskComponent#getAssignedTasks(java.lang.String - * , org.alfresco.service.cmr.workflow.WorkflowTaskState) - */ - @SuppressWarnings("unchecked") - public List getAssignedTasks(final String authority, final WorkflowTaskState state) - { - try - { - return (List) jbpmTemplate.execute(new JbpmCallback() - { - public List doInJbpm(JbpmContext context) - { - // retrieve tasks assigned to authority - List tasks; - if (state.equals(WorkflowTaskState.IN_PROGRESS)) - { - return findActiveTaskInstances(authority, context); - } - else - { - // Note: This method is not implemented by jBPM - tasks = findCompletedTaskInstances(context, authority); - return getWorkflowTasks(tasks); - } - - } - }); - } - catch(JbpmException e) - { - String msg = messageService.getMessage(ERR_GET_ASSIGNED_TASKS, authority, state); - throw new WorkflowException(msg, e); - } - } - - /** - * Gets the completed task list for the specified actor - * - * @param jbpmContext the jbpm context - * @param actorId the actor to retrieve tasks for - * @return the tasks - */ - @SuppressWarnings("unchecked") - private List findCompletedTaskInstances(JbpmContext jbpmContext, String actorId) - { - List result = null; - try - { - Session session = jbpmContext.getSession(); - Query query = session.createQuery(COMPLETED_TASKS_QUERY); - query.setString("actorId", actorId); - result = query.list(); - } - catch (Exception e) - { - String msg = messageService.getMessage(ERR_FIND_COMPLETED_TASK_INSTS, actorId); - throw new JbpmException(msg, e); - } - return result; - } - - @SuppressWarnings("unchecked") - private List findActiveTaskInstances(final String authority, JbpmContext context) - { - Session session = context.getSession(); - Query query = session.getNamedQuery("org.alfresco.repo.workflow.findTaskInstancesByActorId"); - query.setString("actorId", authority); - query.setBoolean("true", true); - List workflowTasks = getWorkflowTasks(session, query.list()); - // Do we need to clear a session here? It takes 3 seconds with 2000 workflows. - // session.clear(); - return workflowTasks; - } - - protected List getWorkflowTasks(Session session, List rows) - { - List workflowTasks = new ArrayList(rows.size()); - - /// ------------------------ - - // Preload data into L1 session - List taskInstanceIds = new ArrayList(rows.size()); - List contextInstanceIds = new ArrayList(rows.size()); - for (Object[] row : rows) - { - TaskInstance ti = (TaskInstance) row[0]; - taskInstanceIds.add(ti.getId()); - ContextInstance ci = (ContextInstance) row[8]; - contextInstanceIds.add(ci.getId()); - } - Map taskInstanceCache = new HashMap(rows.size()); - if (taskInstanceIds.size() > 0) - { - taskInstanceCache = cacheTasks(session, taskInstanceIds); - } - Map variablesCache = new HashMap(rows.size()); - if (contextInstanceIds.size() > 0) - { - variablesCache = cacheVariables(session, contextInstanceIds); - } - taskInstanceIds.clear(); - contextInstanceIds.clear(); - /// ------------------------ - for(Object[] row : rows) - { - WorkflowTask workflowTask = makeWorkflowTask(row, taskInstanceCache, variablesCache); - if(workflowTask !=null ) - { - workflowTasks.add(workflowTask); - } - } - return workflowTasks; - } - - private WorkflowTask makeWorkflowTask(Object[] row, Map taskInstanceCache, Map variablesCache) - { - TaskInstance ti = (TaskInstance) row[0]; - Token token = (Token)row[2]; - ProcessInstance processInstance = (ProcessInstance)row[3]; - Node node = (Node)row[4]; - Task task = (Task)row[5]; - ProcessDefinition processDefinition = (ProcessDefinition)row[6]; - Task startTask = (Task)row[7]; - ContextInstance contextInstance = (ContextInstance) row[8]; - - if (tenantService.isEnabled()) - { - try - { - tenantService.checkDomain(processDefinition.getName()); - } - catch (RuntimeException re) - { - // deliberately skip this one - due to domain mismatch - eg. when querying by group authority - return null; - } - } - // TaskInstance with some precached properties - TaskInstance helperTi = taskInstanceCache.get(ti.getId()); - - @SuppressWarnings("unchecked") - Map variables = variablesCache.get(contextInstance.getId()).getVariables(); - // WorkflowTaskProperies - Map properties = getTaskProperties(helperTi != null ? helperTi : ti, false, variablesCache); - - WorkflowDefinition wfDef = createWorkflowDefinition(processDefinition, startTask); - WorkflowInstance instance = createWorkflowInstance(processInstance, wfDef, null, variables); - WorkflowNode wfNode = createWorkflowNode(node); - WorkflowPath path = createWorkflowPath(token, instance, wfNode); - WorkflowTaskDefinition taskDef = createWorkflowTaskDefinition(task); - return createWorkflowTask(ti, taskDef, path, properties); - } - - private Map cacheVariables(Session session, List ids) - { - // Preload data into L1 session - int batchSize = 800; // Must limit IN clause size! - List batch = new ArrayList(ids.size()); - Map cachedResults = new HashMap(); - for (Long id : ids) - { - batch.add(id); - if (batch.size() >= batchSize) - { - cacheVariablesNoBatch(session, batch, cachedResults); - batch.clear(); - } - } - if (batch.size() > 0) - { - cacheVariablesNoBatch(session, batch, cachedResults); - } - batch.clear(); - return cachedResults; - } - - @SuppressWarnings({ "unchecked", "cast" }) - private void cacheVariablesNoBatch(Session session, List contextInstanceIds, Map variablesCache) - { - Query query = session.getNamedQuery("org.alfresco.repo.workflow.cacheInstanceVariables"); - query.setParameterList("ids", contextInstanceIds); - query.setCacheMode(CacheMode.PUT); - query.setFlushMode(FlushMode.MANUAL); - query.setCacheable(true); - - List results = (List) query.list(); - for (TokenVariableMap tokenVariableMap : results) - { - variablesCache.put(tokenVariableMap.getContextInstance().getId(), tokenVariableMap); - } - } - - private Map cacheTasks(Session session, List ids) - { - // Preload data into L1 session - int batchSize = 800; // Must limit IN clause size! - List batch = new ArrayList(ids.size()); - Map cachedResults = new HashMap(); - for (Long id : ids) - { - batch.add(id); - if (batch.size() >= batchSize) - { - cacheTasksNoBatch(session, batch, cachedResults); - batch.clear(); - } - } - if (batch.size() > 0) - { - cacheTasksNoBatch(session, batch, cachedResults); - } - batch.clear(); - return cachedResults; - } - - @SuppressWarnings({ "unchecked", "cast" }) - private void cacheTasksNoBatch(Session session, List taskInstanceIds, Map returnMap) - { - Query query = session.getNamedQuery("org.alfresco.repo.workflow.cacheTaskInstanceProperties"); - query.setParameterList("ids", taskInstanceIds); - query.setCacheMode(CacheMode.PUT); - query.setFlushMode(FlushMode.MANUAL); - query.setCacheable(true); - - List results = (List) query.list(); - for (TaskInstance taskInstance : results) - { - returnMap.put(taskInstance.getId(), taskInstance); - } - } - - /* - * (non-Javadoc) - * - * @see - * org.alfresco.repo.workflow.TaskComponent#getPooledTasks(java.util.List) - */ - @SuppressWarnings("unchecked") - public List getPooledTasks(final List authorities) - { - try - { - return (List) jbpmTemplate.execute(new JbpmCallback() - { - public List doInJbpm(JbpmContext context) - { - // retrieve pooled tasks for all flattened authorities - TaskMgmtSession taskSession = context.getTaskMgmtSession(); - List tasks = taskSession.findPooledTaskInstances(authorities); - return getWorkflowTasks(tasks); - } - }); - } - catch(JbpmException e) - { - String msg = messageService.getMessage(ERR_GET_POOLED_TASKS, authorities); - throw new WorkflowException(msg, e); - } - } - - /* - * (non-Javadoc) - * - * @see - * org.alfresco.repo.workflow.TaskComponent#queryTasks(org.alfresco.service - * .cmr.workflow.WorkflowTaskFilter) - */ - @SuppressWarnings("unchecked") - public List queryTasks(final WorkflowTaskQuery query) - { - try - { - return (List) jbpmTemplate.execute(new JbpmCallback() - { - public List doInJbpm(JbpmContext context) - { - Session session = context.getSession(); - Criteria criteria = createTaskQueryCriteria(session, query); - List tasks = criteria.list(); - return getWorkflowTasks(tasks); - } - }); - } - catch(JbpmException e) - { - String msg = messageService.getMessage(ERR_QUERY_TASKS, query); - throw new WorkflowException(msg, e); - } - } - - protected List getWorkflowTasks(List tasks) - { - // convert tasks to appropriate service response format - List workflowTasks = new ArrayList(tasks.size()); - for (TaskInstance task : tasks) - { - if (tenantService.isEnabled()) - { - try - { - tenantService.checkDomain(task.getTask().getProcessDefinition().getName()); - } - catch (RuntimeException re) - { - // deliberately skip this one - due to domain mismatch - eg. - // when querying by group authority - continue; - } - } - - WorkflowTask workflowTask = createWorkflowTask(task); - workflowTasks.add(workflowTask); - } - return workflowTasks; - } - - /** - * Construct a JBPM Hibernate query based on the Task Query provided - * - * @param session - * @param query - * @return jbpm hiberate query criteria - */ - private Criteria createTaskQueryCriteria(Session session, WorkflowTaskQuery query) - { - Criteria task = session.createCriteria(TaskInstance.class); - - // task id - if (query.getTaskId() != null) - { - task.add(Restrictions.eq("id", getJbpmId(query.getTaskId()))); - } - - // task state - if (query.getTaskState() != null) - { - WorkflowTaskState state = query.getTaskState(); - if (state == WorkflowTaskState.IN_PROGRESS) - { - task.add(Restrictions.eq("isOpen", true)); - task.add(Restrictions.isNull("end")); - } - else if (state == WorkflowTaskState.COMPLETED) - { - task.add(Restrictions.eq("isOpen", false)); - task.add(Restrictions.isNotNull("end")); - } - } - - // task name - if (query.getTaskName() != null) - { - task.add(Restrictions.eq("name", query.getTaskName().toPrefixString(namespaceService))); - } - - // task actor - if (query.getActorId() != null) - { - task.add(Restrictions.eq("actorId", query.getActorId())); - } - - // task custom properties - if (query.getTaskCustomProps() != null) - { - Map props = query.getTaskCustomProps(); - if (props.size() > 0) - { - Criteria variables = task.createCriteria("variableInstances"); - Disjunction values = Restrictions.disjunction(); - for (Map.Entry prop : props.entrySet()) - { - Conjunction value = Restrictions.conjunction(); - value.add(Restrictions.eq("name", factory.mapQNameToName(prop.getKey()))); - value.add(Restrictions.eq("value", prop.getValue().toString())); - values.add(value); - } - variables.add(values); - } - } - - // process criteria - Criteria process = createProcessCriteria(task, query); - - // process custom properties - if (query.getProcessCustomProps() != null) - { - // TODO: Due to Hibernate bug - // http://opensource.atlassian.com/projects/hibernate/browse/HHH-957 - // it's not possible to perform a sub-select with the criteria api. - // For now issue a - // secondary query and create an IN clause. - Map props = query.getProcessCustomProps(); - if (props.size() > 0) - { - // create criteria for process variables - Criteria variables = session.createCriteria(VariableInstance.class); - variables.setProjection(Projections.distinct(Property.forName("processInstance"))); - Disjunction values = Restrictions.disjunction(); - for (Map.Entry prop : props.entrySet()) - { - Conjunction value = Restrictions.conjunction(); - value.add(Restrictions.eq("name", factory.mapQNameToName(prop.getKey()))); - value.add(Restrictions.eq("value", prop.getValue().toString())); - values.add(value); - } - variables.add(values); - - // note: constrain process variables to same criteria as tasks - createProcessCriteria(variables, query); - - Disjunction processIdCriteria = createProcessIdCriteria(variables); - - // constrain tasks by process list - process = (process == null) ? task.createCriteria("processInstance") : process; - process.add(processIdCriteria); - } - } - - // order by - if (query.getOrderBy() != null) - { - WorkflowTaskQuery.OrderBy[] orderBy = query.getOrderBy(); - for (WorkflowTaskQuery.OrderBy orderByPart : orderBy) - { - if (orderByPart == WorkflowTaskQuery.OrderBy.TaskActor_Asc) - { - task.addOrder(Order.asc("actorId")); - } - else if (orderByPart == WorkflowTaskQuery.OrderBy.TaskActor_Desc) - { - task.addOrder(Order.desc("actorId")); - } - else if (orderByPart == WorkflowTaskQuery.OrderBy.TaskCreated_Asc) - { - task.addOrder(Order.asc("create")); - } - else if (orderByPart == WorkflowTaskQuery.OrderBy.TaskCreated_Desc) - { - task.addOrder(Order.desc("create")); - } - else if (orderByPart == WorkflowTaskQuery.OrderBy.TaskDue_Asc) - { - task.addOrder(Order.asc("dueDate")); - } - else if (orderByPart == WorkflowTaskQuery.OrderBy.TaskDue_Desc) - { - task.addOrder(Order.desc("dueDate")); - } - else if (orderByPart == WorkflowTaskQuery.OrderBy.TaskId_Asc) - { - task.addOrder(Order.asc("id")); - } - else if (orderByPart == WorkflowTaskQuery.OrderBy.TaskId_Desc) - { - task.addOrder(Order.desc("id")); - } - else if (orderByPart == WorkflowTaskQuery.OrderBy.TaskName_Asc) - { - task.addOrder(Order.asc("name")); - } - else if (orderByPart == WorkflowTaskQuery.OrderBy.TaskName_Desc) - { - task.addOrder(Order.desc("name")); - } - else if (orderByPart == WorkflowTaskQuery.OrderBy.TaskState_Asc) - { - task.addOrder(Order.asc("end")); - } - else if (orderByPart == WorkflowTaskQuery.OrderBy.TaskState_Desc) - { - task.addOrder(Order.desc("end")); - } - } - } - - // limit results - if (query.getLimit() != -1) - { - task.setMaxResults(query.getLimit()); - } - - return task; - } - - /** - * @param variables - * @return - */ - private Disjunction createProcessIdCriteria(Criteria variables) - { - // retrieve list of processes matching specified variables - List processList = variables.list(); - Object[] processIds = getProcessIds( processList); - - // ALF-5841 fix - int batch = 0; - List buf = new ArrayList(1000); - - Disjunction ids = Restrictions.disjunction(); - for (Object id : processIds) - { - if (batch < 1000) - { - batch++; - buf.add(id); - } - else - { - ids.add(Restrictions.in("id", buf)); - batch = 0; - buf.clear(); - } - } - - if (!buf.isEmpty()) - { - ids.add(Restrictions.in("id", buf)); - } - return ids; - } - - private Object[] getProcessIds(List processList) - { - ArrayList ids = new ArrayList(processList.size()); - if (processList.isEmpty()) - { - ids.add(new Long(-1)); - } - else - { - for (Object obj : processList) - { - ProcessInstance instance = (ProcessInstance) obj; - ids.add(instance.getId()); - } - } - return ids.toArray(); - } - - /** - * Create process-specific query criteria - * - * @param root - * @param query - * @return - */ - private Criteria createProcessCriteria(Criteria root, WorkflowTaskQuery query) - { - Criteria process = null; - - // process active? - if (query.isActive() != null) - { - process = root.createCriteria("processInstance"); - if (query.isActive()) - { - process.add(Restrictions.isNull("end")); - } - else - { - process.add(Restrictions.isNotNull("end")); - } - } - - // process id - if (query.getProcessId() != null) - { - process = (process == null) ? root.createCriteria("processInstance") : process; - process.add(Restrictions.eq("id", getJbpmId(query.getProcessId()))); - } - - // process definition name - String definitionName = query.getWorkflowDefinitionName(); - if(definitionName!=null) - { - definitionName = createLocalId(definitionName); - } - if(definitionName == null) - { - QName qName = query.getProcessName(); - definitionName= qName == null ? null : qName.toPrefixString(namespaceService); - } - if (definitionName != null) - { - process = (process == null) ? root.createCriteria("processInstance") : process; - Criteria processDef = process.createCriteria("processDefinition"); - String processName = tenantService.getName(definitionName); - processDef.add(Restrictions.eq("name", processName)); - } - - return process; - } - - /** - * Gets a jBPM Task Instance - * - * @param taskSession - * jBPM task session - * @param taskId - * task id - * @return task instance - */ - protected TaskInstance getTaskInstance(TaskMgmtSession taskSession, String taskId) - { - TaskInstance taskInstance = taskSession.getTaskInstance(getJbpmId(taskId)); - - if ((taskInstance != null) && (tenantService.isEnabled())) - { - try - { - // throws exception if domain mismatch - tenantService.checkDomain(taskInstance.getTask().getProcessDefinition().getName()); - } - catch (RuntimeException re) - { - taskInstance = null; - } - } - - if (taskInstance == null) - { - String msg = messageService.getMessage(ERR_GET_TASK_INST, taskId); - throw new WorkflowException(msg); - } - return taskInstance; - } - - /* - * (non-Javadoc) - * - * @see - * org.alfresco.repo.workflow.TaskComponent#updateTask(java.lang.String, - * java.util.Map, java.util.Map, java.util.Map) - */ - public WorkflowTask updateTask(final String taskId, - final Map properties, - final Map> add, final Map> remove) - { - try - { - return (WorkflowTask) jbpmTemplate.execute(new JbpmCallback() - { - @SuppressWarnings("unchecked") - public Object doInJbpm(JbpmContext context) - { - // retrieve task - TaskMgmtSession taskSession = context.getTaskMgmtSession(); - TaskInstance taskInstance = getTaskInstance(taskSession, taskId); - - // create properties to set on task instance - Map newProperties = new HashMap(10); - if(properties!=null) - { - newProperties.putAll(properties); - } - Map existingProperties = getTaskProperties(taskInstance, false); - if (add != null) - { - // add new associations - for (Entry> toAdd : add.entrySet()) - { - // retrieve existing list of noderefs for - // association - QName key = toAdd.getKey(); - Serializable existingValue = newProperties.get(key); - - if (existingValue == null) - { - existingValue = existingProperties.get(key); - } - // make the additions - if (existingValue == null) - { - newProperties.put(key, (Serializable)toAdd.getValue()); - } - else - { - List existingAdd; - if (existingValue instanceof List) - { - existingAdd = (List) existingValue; - } - else - { - existingAdd = new LinkedList(); - existingAdd.add((NodeRef) existingValue); - } - - for (NodeRef nodeRef : toAdd.getValue()) - { - if (!(existingAdd.contains(nodeRef))) - { - existingAdd.add(nodeRef); - } - } - newProperties.put(key, (Serializable) existingAdd); - } - } - } - - if (remove != null) - { - // add new associations - for (Entry> toRemove: remove.entrySet()) - { - // retrieve existing list of noderefs for - // association - QName key = toRemove.getKey(); - Serializable existingValue = newProperties.get(key); - - if (existingValue == null) - { - existingValue = existingProperties.get(key); - } - // make the subtractions - if (existingValue != null) - { - if(existingValue instanceof List) - { - List existingRemove = (List) existingValue; - existingRemove.removeAll(toRemove.getValue()); - newProperties.put(key, (Serializable) existingRemove); - } - else if(toRemove.getValue().contains(existingValue)) - { - newProperties.put(key, new LinkedList()); - } - } - } - } - - // update the task - if (newProperties.isEmpty() == false) - { - setTaskProperties(taskInstance, newProperties); - - // save - ProcessInstance processInstance = taskInstance.getToken().getProcessInstance(); - context.save(processInstance); - } - - // note: the ending of a task may not have signalled (i.e. - // more than one task exists at - // this node) - return createWorkflowTask(taskInstance); - } - }); - } - catch(JbpmException e) - { - String msg = messageService.getMessage(ERR_UPDATE_TASK, taskId); - throw new WorkflowException(msg, e); - } - } - - /* - * (non-Javadoc) - * - * @see org.alfresco.repo.workflow.TaskComponent#startTask(java.lang.String) - */ - public WorkflowTask startTask(String taskId) - { - // TODO: - throw new UnsupportedOperationException(); - } - - /* - * (non-Javadoc) - * - * @see - * org.alfresco.repo.workflow.TaskComponent#suspendTask(java.lang.String) - */ - public WorkflowTask suspendTask(String taskId) - { - // TODO: - throw new UnsupportedOperationException(); - } - - /* - * (non-Javadoc) - * - * @see org.alfresco.repo.workflow.TaskComponent#endTask(java.lang.String, - * java.lang.String) - */ - public WorkflowTask endTask(final String taskId, final String transition) - { - try - { - return (WorkflowTask) jbpmTemplate.execute(new JbpmCallback() - { - public Object doInJbpm(JbpmContext context) - { - // retrieve task - TaskMgmtSession taskSession = context.getTaskMgmtSession(); - TaskInstance taskInstance = getTaskInstance(taskSession, taskId); - - // ensure all mandatory properties have been provided - QName[] missingProps = getMissingMandatoryTaskProperties(taskInstance); - if (missingProps != null && missingProps.length > 0) - { - String props = ""; - for (int i = 0; i < missingProps.length; i++) - { - props += missingProps[i].toString() + ((i < missingProps.length -1) ? "," : ""); - } - String msg = messageService.getMessage(ERR_MANDATORY_TASK_PROPERTIES_MISSING, props); - throw new WorkflowException(msg); - - } - - // signal the transition on the task - if (transition == null) - { - taskInstance.end(); - } - else - { - Node node = taskInstance.getToken().getNode(); - if (node.getLeavingTransition(transition) == null) - { - String msg = messageService.getMessage(ERR_END_TASK_INVALID_TRANSITION, transition, taskId); - throw new WorkflowException(msg); - } - taskInstance.end(transition); - } - - // save - ProcessInstance processInstance = taskInstance.getToken().getProcessInstance(); - context.save(processInstance); - - // note: the ending of a task may not have signalled (i.e. - // more than one task exists at - // this node) - return createWorkflowTask(taskInstance); - } - }); - } - catch(JbpmException e) - { - String msg = messageService.getMessage(ERR_END_TASK, transition, taskId); - throw new WorkflowException(msg, e); - } - } - - /* - * (non-Javadoc) - * - * @see - * org.alfresco.repo.workflow.TaskComponent#getTaskById(java.lang.String) - */ - public WorkflowTask getTaskById(final String taskId) - { - try - { - return (WorkflowTask) jbpmTemplate.execute(new JbpmCallback() - { - public Object doInJbpm(JbpmContext context) - { - // retrieve task - TaskMgmtSession taskSession = context.getTaskMgmtSession(); - TaskInstance taskInstance = getTaskInstance(taskSession, taskId); - return createWorkflowTask(taskInstance); - } - }); - } - catch(JbpmException e) - { - String msg = messageService.getMessage(ERR_GET_TASK_BY_ID, taskId); - throw new WorkflowException(msg, e); - } - } - - @Override - public WorkflowTask getStartTask(final String workflowInstanceId) - { - try - { - return (WorkflowTask) jbpmTemplate.execute(new JbpmCallback() - { - public Object doInJbpm(JbpmContext context) - { - // retrieve process instance - GraphSession graphSession = context.getGraphSession(); - ProcessInstance processInstance = getProcessInstanceIfExists(graphSession, workflowInstanceId); - if(processInstance == null) - { - return null; - } - Task startTask = processInstance.getProcessDefinition().getTaskMgmtDefinition().getStartTask(); - - // retrieve task - Session session = context.getSession(); - Criteria taskCriteria = session.createCriteria(TaskInstance.class); - taskCriteria.add(Restrictions.eq("name", startTask.getName())); - Criteria process = taskCriteria.createCriteria("processInstance"); - process.add(Restrictions.eq("id", processInstance.getId())); - TaskInstance taskInstance = (TaskInstance) taskCriteria.uniqueResult(); - return createWorkflowTask(taskInstance); - } - }); - } - catch (JbpmException e) - { - String msg = messageService.getMessage(ERR_GET_START_TASK, workflowInstanceId); - throw new WorkflowException(msg, e); - } - } - - // - // Helpers... - // - - /** - * Process Definition with accompanying problems - */ - private static class CompiledProcessDefinition - { - public CompiledProcessDefinition(ProcessDefinition def, List problems) - { - this.def = def; - this.problems = new String[problems.size()]; - int i = 0; - for (Problem problem : problems) - { - this.problems[i++] = problem.toString(); - } - } - - protected ProcessDefinition def; - protected String[] problems; - } - - - /** - * Construct a Process Definition from the provided Process Definition - * stream - * - * @param workflowDefinition - * stream to create process definition from - * @param mimetype - * mimetype of stream - * @return process definition - */ - @SuppressWarnings("unchecked") - protected CompiledProcessDefinition compileProcessDefinition(InputStream definitionStream, String mimetype) - { - String actualMimetype = (mimetype == null) ? MimetypeMap.MIMETYPE_ZIP : mimetype; - CompiledProcessDefinition compiledDef = null; - - // parse process definition from jBPM process archive file - - if (actualMimetype.equals(MimetypeMap.MIMETYPE_ZIP)) - { - ZipInputStream zipInputStream = null; - try - { - zipInputStream = new ZipInputStream(definitionStream); - ProcessArchive reader = new ProcessArchive(zipInputStream); - ProcessDefinition def = reader.parseProcessDefinition(); - compiledDef = new CompiledProcessDefinition(def, reader.getProblems()); - } - catch(Exception e) - { - String msg = messageService.getMessage(ERR_COMPILE_PROCESS_DEF_zip); - throw new JbpmException(msg, e); - } - finally - { - if (zipInputStream != null) - { - try - { - zipInputStream.close(); - } - catch (IOException e) - { - // Intentionally empty! - } - } - } - } - - // parse process definition from jBPM xml file - - else if (actualMimetype.equals(MimetypeMap.MIMETYPE_XML)) - { - try - { - JBPMJpdlXmlReader jpdlReader = new JBPMJpdlXmlReader(definitionStream); - ProcessDefinition def = jpdlReader.readProcessDefinition(); - List problems = jpdlReader.getProblems(); - compiledDef = new CompiledProcessDefinition(def, problems); - } - catch(Exception e) - { - String msg = messageService.getMessage(ERR_COMPILE_PROCESS_DEF_XML); - throw new JbpmException(msg, e); - } - } - else - { - String msg = messageService.getMessage(ERR_COMPILE_PROCESS_DEF_UNSUPPORTED, mimetype); - throw new JbpmException(msg); - } - - if (tenantService.isEnabled()) - { - compiledDef.def.setName(tenantService.getName(compiledDef.def.getName())); - } - - return compiledDef; - } - - /** - * Get JBoss JBPM Id from Engine Global Id - * - * @param id - * global id - * @return JBoss JBPM Id - */ - protected long getJbpmId(String id) - { - try - { - String theLong = createLocalId(id); - return new Long(theLong); - } - catch(NumberFormatException e) - { - String msg = messageService.getMessage(ERR_GET_JBPM_ID, id); - throw new WorkflowException(msg, e); - } - } - - /** - * Get the JBoss JBPM Token for the Workflow Path - * - * @param session - * JBoss JBPM Graph Session - * @param pathId - * workflow path id - * @return JBoss JBPM Token - */ - protected Token getWorkflowToken(GraphSession session, String pathId) - { - // extract process id and token path within process - String[] path = pathId.split(WORKFLOW_PATH_SEPERATOR); - if (path.length != 2) - { - String msg = messageService.getMessage(ERR_GET_WORKFLOW_TOKEN_INVALID, pathId); - throw new WorkflowException(msg); - } - - // retrieve jBPM token for workflow position - ProcessInstance processInstance = getProcessInstance(session, path[0]); - String tokenId = path[1].replace(WORKFLOW_TOKEN_SEPERATOR, "/"); - Token token = processInstance.findToken(tokenId); - if (token == null) - { - String msg = messageService.getMessage(ERR_GET_WORKFLOW_TOKEN_NULL, pathId); - throw new WorkflowException(msg); - } - - return token; - } - - /** - * Gets Properties of Task - * - * @param instance task instance - * @param properties properties to set - */ - protected Map getTaskProperties(TaskInstance instance, boolean localProperties) - { - return getTaskProperties(instance, localProperties, null); - } - - /** - * Gets Properties of Task - * - * @param instance task instance - * @param properties properties to set - * @param variablesCache cahce of context instance variables if any exists - */ - @SuppressWarnings("unchecked") - protected Map getTaskProperties(TaskInstance instance, boolean localProperties, Map variablesCache) - { - // retrieve type definition for task - TypeDefinition taskDef = getFullTaskDefinition(instance); - Map taskProperties = taskDef.getProperties(); - Map taskAssocs = taskDef.getAssociations(); - - // build properties from jBPM context (visit all tokens to the root) - Map vars = instance.getVariablesLocally(); - if (!localProperties) - { - ContextInstance context = instance.getContextInstance(); - Token token = instance.getToken(); - while (token != null) - { - TokenVariableMap varMap = null; - if (variablesCache != null && variablesCache.containsKey(context.getId())) - { - varMap = variablesCache.get(context.getId()); - } - else - { - varMap = context.getTokenVariableMap(token); - } - if (varMap != null) - { - Map tokenVars = varMap.getVariablesLocally(); - for (Map.Entry entry : tokenVars.entrySet()) - { - if (!vars.containsKey(entry.getKey())) - { - vars.put(entry.getKey(), entry.getValue()); - } - } - } - token = token.getParent(); - } - } - - // map arbitrary task variables - Map properties = new HashMap(10); - for (Entry entry : vars.entrySet()) - { - String key = entry.getKey(); - QName qname = factory.mapNameToQName(key); - - // add variable, only if part of task definition or locally defined - // on task - boolean isAssoc = taskAssocs.containsKey(qname); - if (taskProperties.containsKey(qname) || isAssoc || instance.hasVariableLocally(key)) - { - Serializable value = convertValue(taskProperties.get(qname), entry.getValue()); - properties.put(qname, value); - } - } - - // map jBPM task instance fields to properties - properties.put(WorkflowModel.PROP_TASK_ID, instance.getId()); - properties.put(WorkflowModel.PROP_DESCRIPTION, instance.getDescription()); - properties.put(WorkflowModel.PROP_START_DATE, instance.getStart()); - properties.put(WorkflowModel.PROP_DUE_DATE, instance.getDueDate()); - properties.put(WorkflowModel.PROP_COMPLETION_DATE, instance.getEnd()); - properties.put(WorkflowModel.PROP_PRIORITY, instance.getPriority()); - properties.put(ContentModel.PROP_CREATED, instance.getCreate()); - properties.put(ContentModel.PROP_OWNER, instance.getActorId()); - - // map jBPM comments - // NOTE: Only use first comment in list - List comments = instance.getComments(); - if (comments != null && comments.size() > 0) - { - properties.put(WorkflowModel.PROP_COMMENT, comments.get(0).getMessage()); - } - - // map jBPM task instance collections to associations - Set pooledActors = instance.getPooledActors(); - if (pooledActors != null) - { - List pooledNodeRefs = new ArrayList(pooledActors.size()); - for (PooledActor pooledActor : pooledActors) - { - NodeRef pooledNodeRef = null; - String pooledActorId = pooledActor.getActorId(); - if (AuthorityType.getAuthorityType(pooledActorId) == AuthorityType.GROUP) - { - pooledNodeRef = mapNameToAuthority(pooledActorId); - } - else - { - pooledNodeRef = mapNameToPerson(pooledActorId); - } - if (pooledNodeRef != null) - { - pooledNodeRefs.add(pooledNodeRef); - } - } - properties.put(WorkflowModel.ASSOC_POOLED_ACTORS, (Serializable)pooledNodeRefs); - } - - return properties; - } - - private TypeDefinition getFullTaskDefinition(TaskInstance instance) - { - Task task = instance.getTask(); - TypeDefinition taskType = factory.getTaskTypeDefinition(task.getName(), task.getStartState() != null); - TypeDefinition taskDef = dictionaryService.getAnonymousType(taskType.getName()); - return taskDef; - } - - /** - * Sets Properties of Task - * - * @param instance - * task instance - * @param properties - * properties to set - */ - protected void setTaskProperties(TaskInstance instance, Map properties) - { - if (properties == null) - { - return; - } - - TypeDefinition taskDef = getFullTaskDefinition(instance); - Map taskProperties = taskDef.getProperties(); - Map taskAssocs = taskDef.getAssociations(); - - // map each parameter to task - for (Entry entry : properties.entrySet()) - { - QName key = entry.getKey(); - Serializable value = entry.getValue(); - - // determine if writing property - // NOTE: some properties map to fields on jBPM task instance whilst - // others are set in the general variable bag on the task - PropertyDefinition propDef = taskProperties.get(key); - if (propDef != null) - { - if (propDef.isProtected()) - { - // NOTE: only write non-protected properties - continue; - } - - // convert property value - if (value instanceof Collection) - { - value = (Serializable) DefaultTypeConverter.INSTANCE.convert(propDef.getDataType(), - (Collection) value); - } - else - { - value = (Serializable)DefaultTypeConverter.INSTANCE.convert(propDef.getDataType(), value); - } - - // convert NodeRefs to JBPMNodes - DataTypeDefinition dataTypeDef = propDef.getDataType(); - if (dataTypeDef.getName().equals(DataTypeDefinition.NODE_REF)) - { - value = convertNodeRefs(propDef.isMultiValued(), value); - } - - // map property to specific jBPM task instance field - if (key.equals(WorkflowModel.PROP_DESCRIPTION)) - { - if (value != null && !(value instanceof String)) - { - throw getInvalidPropertyValueException(key, value); - } - instance.setDescription((String)value); - continue; - } - if (key.equals(WorkflowModel.PROP_DUE_DATE)) - { - if (value != null && !(value instanceof Date)) - { - throw getInvalidPropertyValueException(key, value); - } - instance.setDueDate((Date)value); - continue; - } - else if (key.equals(WorkflowModel.PROP_PRIORITY)) - { - if (!(value instanceof Integer)) - { - throw getInvalidPropertyValueException(key, value); - } - instance.setPriority((Integer)value); - continue; - } - else if (key.equals(WorkflowModel.PROP_COMMENT)) - { - if (!(value instanceof String)) - { - throw getInvalidPropertyValueException(key, value); - } - - // NOTE: Only use first comment in list - final List comments = instance.getComments(); - if (comments != null && comments.size() > 0) - { - // remove existing comments - // TODO: jBPM does not provide assistance here - jbpmTemplate.execute(new JbpmCallback() - { - public Object doInJbpm(JbpmContext context) - { - Session session = context.getSession(); - for (Object obj: comments) - { - Comment comment = (Comment) obj; - comment.getToken().getComments().remove(comment); - session.delete(comment); - } - comments.clear(); - return null; - } - }); - } - instance.addComment((String)value); - continue; - } - else if (key.equals(ContentModel.PROP_OWNER)) - { - if (value != null && !(value instanceof String)) - { - throw getInvalidPropertyValueException(key, value); - } - String actorId = (String)value; - String existingActorId = instance.getActorId(); - if (existingActorId == null || !existingActorId.equals(actorId)) - { - instance.setActorId((String)value, false); - } - continue; - } - } - else - { - // determine if writing association - AssociationDefinition assocDef = taskAssocs.get(key); - if (assocDef != null) - { - // convert association to JBPMNodes - value = convertNodeRefs(assocDef.isTargetMany(), value); - - // map association to specific jBPM task instance field - if (key.equals(WorkflowModel.ASSOC_POOLED_ACTORS)) - { - String[] pooledActors = null; - if (value instanceof JBPMNodeList) - { - JBPMNodeList actors = (JBPMNodeList)value; - pooledActors = new String[actors.size()]; - int i = 0; - for (JBPMNode actor : actors) - { - pooledActors[i++] = mapAuthorityToName(actor.getNodeRef()); - } - } - else if (value instanceof JBPMNode) - { - JBPMNode node = (JBPMNode)value; - pooledActors = new String[] {mapAuthorityToName(node.getNodeRef())}; - } - else - { - throw getInvalidPropertyValueException(key, value); - } - instance.setPooledActors(pooledActors); - continue; - } - } - - // untyped value, perform minimal conversion - else - { - if (value instanceof NodeRef) - { - value = new JBPMNode((NodeRef)value, serviceRegistry); - } - } - } - - // no specific mapping to jBPM task has been established, so place - // into - // the generic task variable bag - String name = factory.mapQNameToName(key); - instance.setVariableLocally(name, value); - } - } - - private WorkflowException getInvalidPropertyValueException(QName key, Serializable value) - { - String msg = messageService.getMessage(ERR_SET_TASK_PROPS_INVALID_VALUE, value, key); - return new WorkflowException(msg); - } - - /** - * Sets Default Properties of Task - * - * @param instance - * task instance - */ - protected void setDefaultTaskProperties(TaskInstance instance) - { - Map existingValues = getTaskProperties(instance, true); - Map defaultValues = new HashMap(); - - // construct an anonymous type that flattens all mandatory aspects - ClassDefinition classDef = getFullTaskDefinition(instance); - Map propertyDefs = classDef.getProperties(); - - // for each property, determine if it has a default value - for (Map.Entry entry : propertyDefs.entrySet()) - { - String defaultValue = entry.getValue().getDefaultValue(); - if (defaultValue != null) - { - if (existingValues.get(entry.getKey()) == null) - { - defaultValues.put(entry.getKey(), defaultValue); - } - } - } - - // special case for task description default value - String description = (String)existingValues.get(WorkflowModel.PROP_DESCRIPTION); - if (description == null || description.length() == 0) - { - description = (String) instance.getContextInstance().getVariable( - factory.mapQNameToName(WorkflowModel.PROP_WORKFLOW_DESCRIPTION)); - if (description != null && description.length() > 0) - { - defaultValues.put(WorkflowModel.PROP_DESCRIPTION, description); - } - else - { - WorkflowTask task = createWorkflowTask(instance); - defaultValues.put(WorkflowModel.PROP_DESCRIPTION, task.getTitle()); - } - } - - // assign the default values to the task - if (defaultValues.size() > 0) - { - setTaskProperties(instance, defaultValues); - } - } - - /** - * Sets default description for the Task - * - * @param instance - * task instance - */ - public void setDefaultStartTaskDescription(TaskInstance instance) - { - String description = instance.getTask().getDescription(); - if (description == null || description.length() == 0) - { - description = (String) instance.getContextInstance().getVariable( - factory.mapQNameToName(WorkflowModel.PROP_WORKFLOW_DESCRIPTION)); - if (description != null && description.length() > 0) - { - Map defaultValues = new HashMap(); - defaultValues.put(WorkflowModel.PROP_DESCRIPTION, description); - setTaskProperties(instance, defaultValues); - } - } - } - - /** - * Initialise Workflow Instance properties - * - * @param startTask - * start task instance - */ - protected void setDefaultWorkflowProperties(TaskInstance startTask) - { - Map taskProperties = getTaskProperties(startTask, true); - - ContextInstance processContext = startTask.getContextInstance(); - String workflowDescriptionName = factory.mapQNameToName(WorkflowModel.PROP_WORKFLOW_DESCRIPTION); - if (!processContext.hasVariable(workflowDescriptionName)) - { - processContext.setVariable(workflowDescriptionName, taskProperties - .get(WorkflowModel.PROP_WORKFLOW_DESCRIPTION)); - } - String workflowDueDateName = factory.mapQNameToName(WorkflowModel.PROP_WORKFLOW_DUE_DATE); - if (!processContext.hasVariable(workflowDueDateName)) - { - processContext.setVariable(workflowDueDateName, taskProperties.get(WorkflowModel.PROP_WORKFLOW_DUE_DATE)); - } - String workflowPriorityName = factory.mapQNameToName(WorkflowModel.PROP_WORKFLOW_PRIORITY); - if (!processContext.hasVariable(workflowPriorityName)) - { - processContext.setVariable(workflowPriorityName, taskProperties.get(WorkflowModel.PROP_WORKFLOW_PRIORITY)); - } - String workflowPackageName = factory.mapQNameToName(WorkflowModel.ASSOC_PACKAGE); - if (!processContext.hasVariable(workflowPackageName)) - { - Serializable packageNodeRef = taskProperties.get(WorkflowModel.ASSOC_PACKAGE); - processContext.setVariable(workflowPackageName, convertNodeRefs(packageNodeRef instanceof List, - packageNodeRef)); - } - String workflowContextName = factory.mapQNameToName(WorkflowModel.PROP_CONTEXT); - if (!processContext.hasVariable(workflowContextName)) - { - Serializable contextRef = taskProperties.get(WorkflowModel.PROP_CONTEXT); - processContext.setVariable(workflowContextName, convertNodeRefs(contextRef instanceof List, contextRef)); - } - } - - /** - * Get missing mandatory properties on Task - * - * @param instance - * task instance - * @return array of missing property names (or null, if none) - */ - protected QName[] getMissingMandatoryTaskProperties(TaskInstance instance) - { - List missingProps = null; - - // retrieve properties of task - Map existingValues = getTaskProperties(instance, false); - - // retrieve definition of task - ClassDefinition classDef = getFullTaskDefinition(instance); - Map propertyDefs = classDef.getProperties(); - Map assocDefs = classDef.getAssociations(); - - // for each property, determine if it is mandatory - for (Map.Entry entry : propertyDefs.entrySet()) - { - QName name = entry.getKey(); - if (!(name.getNamespaceURI().equals(NamespaceService.CONTENT_MODEL_1_0_URI) || (name.getNamespaceURI() - .equals(NamespaceService.SYSTEM_MODEL_1_0_URI)))) - { - boolean isMandatory = entry.getValue().isMandatory(); - if (isMandatory) - { - Object value = existingValues.get(entry.getKey()); - if (value == null || (value instanceof String && ((String)value).length() == 0)) - { - if (missingProps == null) - { - missingProps = new ArrayList(); - } - missingProps.add(entry.getKey()); - } - } - } - } - for (Map.Entry entry : assocDefs.entrySet()) - { - QName name = entry.getKey(); - if (!(name.getNamespaceURI().equals(NamespaceService.CONTENT_MODEL_1_0_URI) || (name.getNamespaceURI() - .equals(NamespaceService.SYSTEM_MODEL_1_0_URI)))) - { - boolean isMandatory = entry.getValue().isTargetMandatory(); - if (isMandatory) - { - Object value = existingValues.get(entry.getKey()); - if (value == null || (value instanceof List && ((List)value).isEmpty())) - { - if (missingProps == null) - { - missingProps = new ArrayList(); - } - missingProps.add(entry.getKey()); - } - } - } - } - - return (missingProps == null) ? null : missingProps.toArray(new QName[missingProps.size()]); - } - - /** - * Attempts to convert a JBPM Object to the correct Alfresco data type - * @param propDef PropertyDefinition - * @param value any Value - * @return - */ - private Serializable convertValue(PropertyDefinition propDef, Object value) - { - if (propDef != null && value instanceof String && Boolean.class.getName().equals(propDef.getDataType().getJavaClassName())) - { - return (Serializable) new BooleanToStringConverter().revert(value); - } - else - { - return convertValue(value); - } - } - - /** - * Convert a jBPM Value to an Alfresco value - * - * @param value - * jBPM value - * @return alfresco value - */ - private Serializable convertValue(Object value) - { - Serializable alfValue = null; - - if (value == null) - { - // NOOP - } - else if (value instanceof JBPMNode) - { - alfValue = ((JBPMNode)value).getNodeRef(); - } - else if (value instanceof JBPMNodeList) - { - JBPMNodeList nodes = (JBPMNodeList)value; - List nodeRefs = new ArrayList(nodes.size()); - for (JBPMNode node : nodes) - { - nodeRefs.add(node.getNodeRef()); - } - alfValue = (Serializable)nodeRefs; - } - else if (value instanceof Serializable) - { - alfValue = (Serializable)value; - } - else - { - String msg = messageService.getMessage(ERR_CONVERT_VALUE, value); - throw new WorkflowException(msg); - } - return alfValue; - } - - /** - * Convert a Repository association to JBPMNodeList or JBPMNode - * - * @param isMany - * true => force conversion to list - * @param value - * value to convert - * @return JBPMNodeList or JBPMNode - */ - @SuppressWarnings("unchecked") - private Serializable convertNodeRefs(boolean isMany, Serializable value) - { - if (value instanceof NodeRef) - { - if (isMany) - { - // convert single node ref to list of node refs - JBPMNodeList values = new JBPMNodeList(); - values.add(new JBPMNode((NodeRef)value, serviceRegistry)); - value = values; - } - else - { - value = new JBPMNode((NodeRef)value, serviceRegistry); - } - } - else if (value instanceof List) - { - if (isMany) - { - JBPMNodeList values = new JBPMNodeList(); - for (NodeRef nodeRef : (List)value) - { - values.add(new JBPMNode(nodeRef, serviceRegistry)); - } - value = values; - } - else - { - List nodeRefs = (List)value; - value = (nodeRefs.size() == 0 ? null : new JBPMNode(nodeRefs.get(0), serviceRegistry)); - } - } - - return value; - } - - /** - * Convert person name to an Alfresco Person - * - * @param names - * the person name to convert - * @return the Alfresco person - */ - private NodeRef mapNameToPerson(String name) - { - NodeRef authority = null; - if (name != null) - { - // TODO: Should this be an exception? - if (personService.personExists(name)) - { - authority = personService.getPerson(name); - } - } - return authority; - } - - /** - * Convert authority name to an Alfresco Authority - * - * @param names - * the authority names to convert - * @return the Alfresco authorities - */ - private NodeRef mapNameToAuthority(String name) - { - NodeRef authority = null; - if (name != null) - { - // TODO: Should this be an exception? - if (authorityDAO.authorityExists(name)) - { - authority = authorityDAO.getAuthorityNodeRefOrNull(name); - } - } - return authority; - } - - /** - * Convert Alfresco authority to actor id - * - * @param authority - * @return actor id - */ - private String mapAuthorityToName(NodeRef authority) - { - String name = null; - QName type = nodeService.getType(authority); - - if (dictionaryService.isSubClass(type, ContentModel.TYPE_PERSON)) - { - name = (String)nodeService.getProperty(authority, ContentModel.PROP_USERNAME); - } - else - { - name = authorityDAO.getAuthorityName(authority); - } - return name; - } - - /** - * Get an I18N Label for a workflow item - * - * @param displayId - * message resource id lookup - * @param labelKey - * label to lookup (title or description) - * @param defaultLabel - * default value if not found in message resource bundle - * @return the label - */ - private String getLabel(String displayId, String labelKey, String defaultLabel) - { - String key = StringUtils.replace(displayId, ":", "_"); - key += "." + labelKey; - String label = messageService.getMessage(key); - - return (label == null) ? defaultLabel : label; - } - - /** - * Gets the Company Home - * - * @return company home node ref - */ - private NodeRef getCompanyHome() - { - if (tenantService.isEnabled()) - { - try - { - return tenantService.getRootNode(nodeService, serviceRegistry.getSearchService(), namespaceService, - companyHomePath, nodeService.getRootNode(companyHomeStore)); - } - catch (RuntimeException re) - { - String msg = messageService.getMessage(ERR_GET_COMPANY_HOME_INVALID, companyHomePath); - throw new IllegalStateException(msg, re); - } - } - else - { - List refs = unprotectedSearchService.selectNodes(nodeService.getRootNode(companyHomeStore), - companyHomePath, null, namespaceService, false); - if (refs.size() != 1) - { - String msg = messageService.getMessage(ERR_GET_COMPANY_HOME_MULTIPLE, companyHomePath, refs.size()); - throw new IllegalStateException(msg); - } - return refs.get(0); - } - } - - // - // Workflow Data Object Creation... - // - - /** - * Creates a Workflow Path - * - * @param token - * JBoss JBPM Token - * @param wfInstance - * @param node - * @return Workflow Path - */ - protected WorkflowPath createWorkflowPath(Token token) - { - if(token == null) - return null; - WorkflowInstance wfInstance = createWorkflowInstance(token.getProcessInstance()); - WorkflowNode node = createWorkflowNode(token.getNode()); - return createWorkflowPath(token, wfInstance, node); - } - - /** - * Creates a Workflow Path - * - * @param token - * JBoss JBPM Token - * @param wfInstance - * @param node - * @return Workflow Path - */ - protected WorkflowPath createWorkflowPath(Token token, WorkflowInstance wfInstance, WorkflowNode node) - { - String tokenId = token.getFullName().replace("/", WORKFLOW_TOKEN_SEPERATOR); - String id = token.getProcessInstance().getId() + WORKFLOW_PATH_SEPERATOR + tokenId; - boolean isActive = !token.hasEnded(); - return factory.createPath(id, wfInstance, node, isActive); - } - - /** - * Creates a Workflow Node - * - * @param node - * JBoss JBPM Node - * @return Workflow Node - */ - protected WorkflowNode createWorkflowNode(Node node) - { - if(node==null) - return null; - String processName = node.getProcessDefinition().getName(); - String name = node.getName(); - String type = getRealNode(node).getClass().getSimpleName(); - // TODO: Is there a formal way of determing if task node? - boolean isTaskNode = type.equals("TaskNode"); - List transitions = node.getLeavingTransitions(); - List wfTransitions; - if (transitions != null) - { - wfTransitions = new ArrayList(transitions.size()); - for (Transition transition : transitions) - { - wfTransitions.add(createWorkflowTransition(transition)); - } - } - else - { - wfTransitions = Collections.emptyList(); - } - WorkflowTransition[] transArr = wfTransitions.toArray(new WorkflowTransition[0]); - return factory.createNode(name, processName, name, null, type, isTaskNode, transArr); - } - - /** - * Create a Workflow Transition - * - * @param transition - * JBoss JBPM Transition - * @return Workflow Transition - */ - protected WorkflowTransition createWorkflowTransition(Transition transition) - { - if(transition==null) - return null; - String id = transition.getName(); - Node node = transition.getFrom(); - boolean isDefault = node.getDefaultLeavingTransition().equals(transition); - String title; - String description; - if (id == null || id.length() == 0) - { - title = getLabel(DEFAULT_TRANSITION_LABEL, TITLE_LABEL, id); - description = getLabel(DEFAULT_TRANSITION_LABEL, DESC_LABEL, title); - } - else - { - String nodeName = node.getName(); - String processName = node.getProcessDefinition().getName(); - title = getLabel(processName + ".node." + nodeName + ".transition." + id, TITLE_LABEL, id); - description = getLabel(processName + ".node." + nodeName + ".transition." + id, DESC_LABEL, title); - } - return new WorkflowTransition(id, title, description, isDefault); - } - - protected WorkflowInstance createWorkflowInstance(ProcessInstance instance) - { - return createWorkflowInstance(instance, null); - } - - @SuppressWarnings("unchecked") - private WorkflowInstance createWorkflowInstance(ProcessInstance instance, Date endDate) - { - if(instance == null) - return null; - - Map variables = instance.getContextInstance().getVariables(); - WorkflowDefinition definition = createWorkflowDefinition(instance.getProcessDefinition()); - return createWorkflowInstance(instance, definition, endDate, variables); - } - - /** - * Creates a Workflow Instance - * - * @param instance - * JBoss JBPM Process Instance - * @param endDate - * @param variables - * @return Workflow instance - */ - protected WorkflowInstance createWorkflowInstance(ProcessInstance instance, WorkflowDefinition definition, Date endDate, Map variables) - { - if(instance == null) - return null; - String id = Long.toString(instance.getId()); - Date startDate = instance.getStart(); - boolean isActive = false; - if (endDate == null) - { - isActive = !instance.hasEnded(); - endDate = instance.getEnd(); - } - return factory.createInstance(id, definition, variables, isActive, startDate, endDate); - } - - /** - * Creates a Workflow Definition - * - * @param definition - * JBoss Process Definition - * @return Workflow Definition - */ - protected WorkflowDefinition createWorkflowDefinition(ProcessDefinition definition) - { - if(definition==null) - return null; - Task startTask = definition.getTaskMgmtDefinition().getStartTask(); - return createWorkflowDefinition(definition, startTask); - } - - /** - * Creates a Workflow Definition - * - * @param definition - * JBoss Process Definition - * @return Workflow Definition - */ - private WorkflowDefinition createWorkflowDefinition(ProcessDefinition definition, Task startTask) - { - if(definition==null) - return null; - String id = Long.toString(definition.getId()); - String name = definition.getName(); - int version = definition.getVersion(); - WorkflowTaskDefinition startTaskDef = createWorkflowTaskDefinition(startTask); - return factory.createDefinition(id, name, version, name, null, startTaskDef); - } - - /** - * * Creates a Workflow Task - * @param task - * @return - */ - protected WorkflowTask createWorkflowTask(TaskInstance task) - { - WorkflowPath path = createWorkflowPath(task.getToken()); - Map properties = getTaskProperties(task, false); - WorkflowTaskDefinition definition = createWorkflowTaskDefinition(task.getTask()); - return createWorkflowTask(task, definition, path, properties); - } - - /** - * Creates a Workflow Task - * - * @param task - * JBoss Task Instance - * @param taskDef - * @param path - * @param properties - * @return Workflow Task - */ - private WorkflowTask createWorkflowTask(TaskInstance task, WorkflowTaskDefinition definition, WorkflowPath path, Map properties) - { - if(task == null) - return null; - String processName = task.getTask().getProcessDefinition().getName(); - if (tenantService.isEnabled()) - { - tenantService.checkDomain(processName); // throws exception if - // domain mismatch - } - String id = Long.toString(task.getId()); - String name = task.getName(); - WorkflowTaskState state = getWorkflowTaskState(task); - return factory.createTask(id, definition, name, null, null, state, path, properties); - } - - /** - * Creates a Workflow Task Definition - * - * @param task - * JBoss JBPM Task - * @return Workflow Task Definition - */ - protected WorkflowTaskDefinition createWorkflowTaskDefinition(Task task) - { - if (task == null) - return null; - String id = task.getName(); - boolean isStart = task.getStartState() != null; - Node node = isStart ? task.getStartState() : task.getTaskNode(); - WorkflowNode wfNode = createWorkflowNode(node); - return factory.createTaskDefinition(id, wfNode, id, isStart); - } - - /** - * Creates a Workflow Deployment - * - * @param compiledDef - * compiled JBPM process definition - * @return workflow deployment - */ - protected WorkflowDeployment createWorkflowDeployment(CompiledProcessDefinition compiledDef) - { - WorkflowDefinition definition = createWorkflowDefinition(compiledDef.def); - String[] problems = compiledDef.problems; - return factory.createDeployment(definition, problems); - } - - /** - * Creates a Workflow Timer - * - * @param timer - * jBPM Timer - * @return workflow timer - */ - protected WorkflowTimer createWorkflowTimer(Timer timer) - { - if(timer==null) - return null; - - WorkflowPath path = createWorkflowPath(timer.getToken()); - - WorkflowTask workflowTask = null; - TaskInstance taskInstance = timer.getTaskInstance(); - if (taskInstance != null) - { - workflowTask = createWorkflowTask(taskInstance); - } - - return factory.createWorkflowTimer(new Long(timer.getId()).toString(), timer.getName(), - timer.getException(), timer.getDueDate(), path, workflowTask); - } - - /** - * Get the Workflow Task State for the specified JBoss JBPM Task - * - * @param task - * task - * @return task state - */ - protected WorkflowTaskState getWorkflowTaskState(TaskInstance task) - { - if (task.hasEnded()) - { - return WorkflowTaskState.COMPLETED; - } - else - { - return WorkflowTaskState.IN_PROGRESS; - } - } - - /** - * Helper to retrieve the real jBPM Node - * - * @param node - * Node - * @return real Node (i.e. the one that's not a Hibernate proxy) - */ - private Node getRealNode(Node node) - { - if (node instanceof HibernateProxy) - { - Node realNode = (Node)((HibernateProxy)node).getHibernateLazyInitializer().getImplementation(); - return realNode; - } - else - { - return node; - } - } - - @Override - protected QName getDefaultStartTaskType() - { - return WorkflowModel.TYPE_START_TASK; - } - -} +/* + * 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.workflow.jbpm; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Serializable; +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.Map.Entry; +import java.util.zip.ZipInputStream; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authority.AuthorityDAO; +import org.alfresco.repo.workflow.AlfrescoBpmEngine; +import org.alfresco.repo.workflow.WorkflowConstants; +import org.alfresco.repo.workflow.WorkflowEngine; +import org.alfresco.repo.workflow.WorkflowModel; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.dictionary.AssociationDefinition; +import org.alfresco.service.cmr.dictionary.ClassDefinition; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.service.cmr.dictionary.TypeDefinition; +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.cmr.repository.datatype.DefaultTypeConverter; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.cmr.security.AuthorityType; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.cmr.workflow.WorkflowDefinition; +import org.alfresco.service.cmr.workflow.WorkflowDeployment; +import org.alfresco.service.cmr.workflow.WorkflowException; +import org.alfresco.service.cmr.workflow.WorkflowInstance; +import org.alfresco.service.cmr.workflow.WorkflowNode; +import org.alfresco.service.cmr.workflow.WorkflowPath; +import org.alfresco.service.cmr.workflow.WorkflowTask; +import org.alfresco.service.cmr.workflow.WorkflowTaskDefinition; +import org.alfresco.service.cmr.workflow.WorkflowTaskQuery; +import org.alfresco.service.cmr.workflow.WorkflowTaskState; +import org.alfresco.service.cmr.workflow.WorkflowTimer; +import org.alfresco.service.cmr.workflow.WorkflowTransition; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.GUID; +import org.alfresco.util.collections.CollectionUtils; +import org.alfresco.util.collections.Function; +import org.hibernate.CacheMode; +import org.hibernate.Criteria; +import org.hibernate.FlushMode; +import org.hibernate.Query; +import org.hibernate.Session; +import org.hibernate.criterion.Conjunction; +import org.hibernate.criterion.Disjunction; +import org.hibernate.criterion.Order; +import org.hibernate.criterion.Projections; +import org.hibernate.criterion.Property; +import org.hibernate.criterion.Restrictions; +import org.hibernate.proxy.HibernateProxy; +import org.jbpm.JbpmContext; +import org.jbpm.JbpmException; +import org.jbpm.context.exe.ContextInstance; +import org.jbpm.context.exe.TokenVariableMap; +import org.jbpm.context.exe.VariableInstance; +import org.jbpm.context.exe.converter.BooleanToStringConverter; +import org.jbpm.db.GraphSession; +import org.jbpm.db.TaskMgmtSession; +import org.jbpm.file.def.FileDefinition; +import org.jbpm.graph.def.Event; +import org.jbpm.graph.def.Node; +import org.jbpm.graph.def.ProcessDefinition; +import org.jbpm.graph.def.Transition; +import org.jbpm.graph.exe.Comment; +import org.jbpm.graph.exe.ExecutionContext; +import org.jbpm.graph.exe.ProcessInstance; +import org.jbpm.graph.exe.Token; +import org.jbpm.job.Timer; +import org.jbpm.jpdl.par.ProcessArchive; +import org.jbpm.jpdl.xml.Problem; +import org.jbpm.taskmgmt.def.Task; +import org.jbpm.taskmgmt.def.TaskMgmtDefinition; +import org.jbpm.taskmgmt.exe.PooledActor; +import org.jbpm.taskmgmt.exe.TaskInstance; +import org.springframework.dao.DataAccessException; +import org.springframework.util.StringUtils; +import org.springmodules.workflow.jbpm31.JbpmAccessor; +import org.springmodules.workflow.jbpm31.JbpmCallback; +import org.springmodules.workflow.jbpm31.JbpmTemplate; + + +/** + * JBoss JBPM based implementation of: + * + * Workflow Definition Component Workflow Component Task Component + * + * @author davidc + */ +public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine +{ + // Implementation dependencies + protected NodeService nodeService; + protected ServiceRegistry serviceRegistry; + protected PersonService personService; + protected AuthorityDAO authorityDAO; + protected JbpmTemplate jbpmTemplate; + protected SearchService unprotectedSearchService; + + // Company Home + protected StoreRef companyHomeStore; + protected String companyHomePath; + + // Note: jBPM query which is not provided out-of-the-box + // TODO: Check jBPM 3.2 and get this implemented in jBPM + private final static String COMPLETED_TASKS_QUERY = "select ti " + "from org.jbpm.taskmgmt.exe.TaskInstance as ti " + + "where ti.actorId = :actorId " + "and ti.isOpen = false " + "and ti.end is not null"; + + // Note: jBPM query which is not provided out-of-the-box + // TODO: Check jBPMg future and get this implemented in jBPM + private final static String PROCESS_TIMERS_QUERY = "select timer " + "from org.jbpm.job.Timer timer " + + "where timer.processInstance = :process "; + + // Workflow Path Seperators + private final static String WORKFLOW_PATH_SEPERATOR = "-"; + private final static String WORKFLOW_TOKEN_SEPERATOR = "@"; + + // I18N labels + private final static String TITLE_LABEL = "title"; + private final static String DESC_LABEL = "description"; + private final static String DEFAULT_TRANSITION_LABEL = "bpm_businessprocessmodel.transition"; + + private static final String ERR_MANDATORY_TASK_PROPERTIES_MISSING = "Jbpm.engine.mandatory.properties.missing"; + private static final String ERR_DEPLOY_WORKFLOW= "jbpm.engine.deploy.workflow.error"; + private static final String ERR_IS_WORKFLOW_DEPLOYED = "jbpm.engine.is.workflow.deployed.error"; + private static final String ERR_UNDEPLOY_WORKFLOW = "jbpm.engine.undeploy.workflow.error"; + private static final String ERR_GET_WORKFLOW_DEF = "jbpm.engine.get.workflow.definition.error"; + private static final String ERR_GET_WORKFLOW_DEF_BY_ID = "jbpm.engine.get.workflow.definition.by.id.error"; + private static final String ERR_GET_WORKFLOW_DEF_BY_NAME = "jbpm.engine.get.workflow.definition.by.name.error"; + private static final String ERR_GET_ALL_DEFS_BY_NAME = "jbpm.engine.get.all.workflow.definitions.by.name.error"; + private static final String ERR_GET_DEF_IMAGE = "jbpm.engine.get.workflow.definition.image.error"; + private static final String ERR_GET_TASK_DEFS = "jbpm.engine.get.task.definitions.error"; + private static final String ERR_GET_PROCESS_DEF = "jbpm.engine.get.process.definition.error"; + private static final String ERR_START_WORKFLOW = "jbpm.enginestart.workflow.error"; + private static final String ERR_GET_ACTIVE_WORKFLOW_INSTS = "jbpm.engine.get.active.workflows.error"; + private static final String ERR_GET_WORKFLOW_INST_BY_ID = "jbpm.engine.get.workflow.instance.by.id.error"; + private static final String ERR_GET_PROCESS_INSTANCE = "jbpm.engine.get.process.instance.error"; + private static final String ERR_GET_WORKFLOW_PATHS = "jbpm.engine.get.workflow.paths.error"; + private static final String ERR_GET_PATH_PROPERTIES = "jbpm.engine.get.path.properties.error"; + private static final String ERR_CANCEL_WORKFLOW = "jbpm.engine.cancel.workflow.error"; + private static final String ERR_DELETE_WORKFLOW = "jbpm.engine.delete.workflow.error"; + private static final String ERR_SIGNAL_TRANSITION = "jbpm.engine.signal.transition.error"; + protected static final String ERR_INVALID_EVENT = "jbpm.engine.invalid.event"; + private static final String ERR_FIRE_EVENT = "jbpm.engine.fire.event.error"; + private static final String ERR_GET_TASKS_FOR_PATH = "jbpm.engine.get.tasks.for.path.error"; + private static final String ERR_GET_TIMERS = "jbpm.engine.get.timers.error"; + protected static final String ERR_FIND_COMPLETED_TASK_INSTS = "jbpm.engine.find.completed.task.instances.error"; + private static final String ERR_GET_ASSIGNED_TASKS = "jbpm.engine.get.assigned.tasks.error"; + private static final String ERR_GET_POOLED_TASKS = "jbpm.engine.get.pooled.tasks.error"; + private static final String ERR_QUERY_TASKS = "jbpm.engine.query.tasks.error"; + private static final String ERR_GET_TASK_INST = "jbpm.engine.get.task.instance.error"; + private static final String ERR_UPDATE_TASK = "jbpm.engine.update.task.error"; + protected static final String ERR_END_TASK_INVALID_TRANSITION ="jbpm.engine.end.task.invalid.transition"; + private static final String ERR_END_TASK = "jbpm.engine.end.task.error"; + private static final String ERR_GET_TASK_BY_ID = "jbpm.engine.get.task.by.id.error"; + private static final String ERR_GET_START_TASK = "jbpm.engine.get.start.task.error"; + private static final String ERR_COMPILE_PROCESS_DEF_zip = "jbpm.engine.compile.process.definition.zip.error"; + private static final String ERR_COMPILE_PROCESS_DEF_XML = "jbpm.engine.compile.process.definition.xml.error"; + private static final String ERR_COMPILE_PROCESS_DEF_UNSUPPORTED = "jbpm.engine.compile.process.definition.unsupported.error"; + private static final String ERR_GET_JBPM_ID = "jbpm.engine.get.jbpm.id.error"; + private static final String ERR_GET_WORKFLOW_TOKEN_INVALID = "jbpm.engine.get.workflow.token.invalid"; + private static final String ERR_GET_WORKFLOW_TOKEN_NULL = "jbpm.engine.get.workflow.token.is.null"; + private static final String ERR_SET_TASK_PROPS_INVALID_VALUE = "jbpm.engine.set.task.properties.invalid.value"; + private static final String ERR_CONVERT_VALUE = "jbpm.engine.convert.value.error"; + private static final String ERR_GET_COMPANY_HOME_INVALID = "jbpm.engine.get.company.home.invalid"; + private static final String ERR_GET_COMPANY_HOME_MULTIPLE = "jbpm.engine.get.company.home.multiple"; + + // engine ID + public static final String ENGINE_ID = "jbpm"; + + /** + * Sets the JBPM Template used for accessing JBoss JBPM in the correct + * context + * + * @param jbpmTemplate + */ + public void setJBPMTemplate(JbpmTemplate jbpmTemplate) + { + this.jbpmTemplate = jbpmTemplate; + } + + /** + * Sets the Node Service + * + * @param nodeService + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * Sets the Person Service + * + * @param personService + */ + public void setPersonService(PersonService personService) + { + this.personService = personService; + } + + /** + * Sets the Authority DAO + * + * @param authorityDAO + */ + public void setAuthorityDAO(AuthorityDAO authorityDAO) + { + this.authorityDAO = authorityDAO; + } + + /** + * Sets the Service Registry + * + * @param serviceRegistry + */ + public void setServiceRegistry(ServiceRegistry serviceRegistry) + { + this.serviceRegistry = serviceRegistry; + } + + /** + * Sets the Company Home Path + * + * @param companyHomePath + */ + public void setCompanyHomePath(String companyHomePath) + { + this.companyHomePath = companyHomePath; + } + + /** + * Sets the Company Home Store + * + * @param companyHomeStore + */ + public void setCompanyHomeStore(String companyHomeStore) + { + this.companyHomeStore = new StoreRef(companyHomeStore); + } + + /** + * Set the unprotected search service - so we can find the node ref for + * company home when folk do not have read access to company home TODO: + * review use with DC + * + * @param unprotectedSearchService + */ + public void setUnprotectedSearchService(SearchService unprotectedSearchService) + { + this.unprotectedSearchService = unprotectedSearchService; + } + + // + // Workflow Definition... + // + + /* + * @see org.alfresco.repo.workflow.WorkflowComponent#deployDefinition(java.io.InputStream, java.lang.String) + */ + public WorkflowDeployment deployDefinition(final InputStream workflowDefinition, final String mimetype) + { + return deployDefinition(workflowDefinition, mimetype, null); + } + + /* + * @see org.alfresco.repo.workflow.WorkflowComponent#deployDefinition(java.io.InputStream, java.lang.String, java.lang.String) + */ + public WorkflowDeployment deployDefinition(final InputStream workflowDefinition, final String mimetype, final String name) + { + try + { + return (WorkflowDeployment)jbpmTemplate.execute(new JbpmCallback() + { + public Object doInJbpm(JbpmContext context) + { + // construct process definition + CompiledProcessDefinition compiledDef = compileProcessDefinition(workflowDefinition, mimetype); + + // deploy the parsed definition + context.deployProcessDefinition(compiledDef.def); + + // return deployed definition + WorkflowDeployment workflowDeployment = createWorkflowDeployment(compiledDef); + return workflowDeployment; + } + }); + } + catch(JbpmException e) + { + String msg = messageService.getMessage(ERR_DEPLOY_WORKFLOW); + throw new WorkflowException(msg, e); + } + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.WorkflowDefinitionComponent#isDefinitionDeployed + * (java.io.InputStream, java.lang.String) + */ + public boolean isDefinitionDeployed(final InputStream workflowDefinition, final String mimetype) + { + try + { + return (Boolean) jbpmTemplate.execute(new JbpmCallback() + { + public Boolean doInJbpm(JbpmContext context) + { + // create process definition from input stream + CompiledProcessDefinition processDefinition = compileProcessDefinition(workflowDefinition, mimetype); + + // retrieve process definition from Alfresco Repository + GraphSession graphSession = context.getGraphSession(); + String definitionName = processDefinition.def.getName(); + ProcessDefinition existingDefinition = graphSession.findLatestProcessDefinition(definitionName); + return existingDefinition != null; + } + }); + } + catch(JbpmException e) + { + String msg = messageService.getMessage(ERR_IS_WORKFLOW_DEPLOYED); + throw new WorkflowException(msg, e); + } + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.WorkflowDefinitionComponent#undeployDefinition + * (java.lang.String) + */ + public void undeployDefinition(final String workflowDefinitionId) + { + try + { + jbpmTemplate.execute(new JbpmCallback() + { + public Object doInJbpm(JbpmContext context) + { + // retrieve process definition + GraphSession graphSession = context.getGraphSession(); + ProcessDefinition processDefinition = getProcessDefinition(graphSession, workflowDefinitionId); + + // undeploy + // NOTE: jBPM deletes all "in-flight" processes too + // TODO: Determine if there's a safer undeploy we can expose + // via the WorkflowService contract + graphSession.deleteProcessDefinition(processDefinition); + + // we're done + return null; + } + }); + } + catch(JbpmException e) + { + String msg = messageService.getMessage(ERR_UNDEPLOY_WORKFLOW, workflowDefinitionId); + throw new WorkflowException(msg, e); + } + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + public List getDefinitions() + { + try + { + return (List) jbpmTemplate.execute(new JbpmCallback() + { + public Object doInJbpm(JbpmContext context) + { + GraphSession graphSession = context.getGraphSession(); + List processDefs = graphSession.findLatestProcessDefinitions(); + return getValidDefinitions(processDefs); + } + }); + } + catch (JbpmException e) + { + String msg = messageService.getMessage(ERR_GET_WORKFLOW_DEF); + throw new WorkflowException(msg, e); + } + } + + private List getValidDefinitions(Collection definitions) + { + List filteredDefs = factory.filterByDomain(definitions, new Function() + { + public String apply(ProcessDefinition definition) + { + return definition.getName(); + } + }); + return convertDefinitions(filteredDefs); + } + + private List convertDefinitions(Collection definitions) + { + return CollectionUtils.transform(definitions, new Function() + { + public WorkflowDefinition apply(ProcessDefinition value) + { + return createWorkflowDefinition(value); + } + }); + } + + private List convertWorkflows(Collection instances) + { + return CollectionUtils.transform(instances, new Function() + { + public WorkflowInstance apply(ProcessInstance value) + { + return createWorkflowInstance(value); + } + }); + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.WorkflowDefinitionComponent#getDefinitions() + */ + @SuppressWarnings("unchecked") + public List getAllDefinitions() + { + try + { + return (List) jbpmTemplate.execute(new JbpmCallback() + { + public Object doInJbpm(JbpmContext context) + { + GraphSession graphSession = context.getGraphSession(); + List processDefs = graphSession.findAllProcessDefinitions(); + return getValidDefinitions(processDefs); + } + }); + } + catch (JbpmException e) + { + String msg = messageService.getMessage(ERR_GET_WORKFLOW_DEF); + throw new WorkflowException(msg, e); + } + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.WorkflowDefinitionComponent#getDefinitionById + * (java.lang.String) + */ + public WorkflowDefinition getDefinitionById(final String workflowDefinitionId) + { + try + { + return (WorkflowDefinition)jbpmTemplate.execute(new JbpmCallback() + { + public Object doInJbpm(JbpmContext context) + { + // retrieve process + GraphSession graphSession = context.getGraphSession(); + ProcessDefinition processDefinition = getProcessDefinition(graphSession, workflowDefinitionId); + return createWorkflowDefinition(processDefinition); + } + }); + } + catch(JbpmException e) + { + String msg = messageService.getMessage(ERR_GET_WORKFLOW_DEF_BY_ID, workflowDefinitionId); + throw new WorkflowException(msg, e); + } + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.WorkflowComponent#getDefinitionByName(java + * .lang.String) + */ + public WorkflowDefinition getDefinitionByName(final String workflowName) + { + try + { + return (WorkflowDefinition)jbpmTemplate.execute(new JbpmCallback() + { + public Object doInJbpm(JbpmContext context) + { + GraphSession graphSession = context.getGraphSession(); + String definitionName = tenantService.getName(createLocalId(workflowName)); + ProcessDefinition processDef = graphSession.findLatestProcessDefinition(definitionName); + return processDef == null ? null : createWorkflowDefinition(processDef); + } + }); + } + catch(JbpmException e) + { + String msg = messageService.getMessage(ERR_GET_WORKFLOW_DEF_BY_NAME, workflowName); + throw new WorkflowException(msg, e); + } + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.WorkflowComponent#getAllDefinitionsByName( + * java.lang.String) + */ + @SuppressWarnings("unchecked") + public List getAllDefinitionsByName(final String workflowName) + { + try + { + return (List)jbpmTemplate.execute(new JbpmCallback() + { + @SuppressWarnings("synthetic-access") + public Object doInJbpm(JbpmContext context) + { + GraphSession graphSession = context.getGraphSession(); + String definitionName = tenantService.getName(createLocalId(workflowName)); + List processDefs = graphSession.findAllProcessDefinitionVersions(definitionName); + return convertDefinitions(processDefs); + } + }); + } + catch(JbpmException e) + { + String msg = messageService.getMessage(ERR_GET_ALL_DEFS_BY_NAME, workflowName); + throw new WorkflowException(msg, e); + } + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.WorkflowComponent#getDefinitionImage(java. + * lang.String) + */ + public byte[] getDefinitionImage(final String workflowDefinitionId) + { + try + { + return (byte[])jbpmTemplate.execute(new JbpmCallback() + { + @SuppressWarnings("synthetic-access") + public Object doInJbpm(JbpmContext context) + { + GraphSession graphSession = context.getGraphSession(); + ProcessDefinition processDefinition = getProcessDefinition(graphSession, workflowDefinitionId); + FileDefinition fileDefinition = processDefinition.getFileDefinition(); + return (fileDefinition == null) ? null : fileDefinition.getBytes("processimage.jpg"); + } + }); + } + catch(JbpmException e) + { + String msg = messageService.getMessage(ERR_GET_DEF_IMAGE, workflowDefinitionId); + throw new WorkflowException(msg, e); + } + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.WorkflowComponent#getAllTaskDefinitions(java + * .lang.String) + */ + @SuppressWarnings("unchecked") + public List getTaskDefinitions(final String workflowDefinitionId) + { + try + { + return (List)jbpmTemplate.execute(new JbpmCallback() + { + public Object doInJbpm(JbpmContext context) + { + // retrieve process + GraphSession graphSession = context.getGraphSession(); + ProcessDefinition processDefinition = getProcessDefinition(graphSession, workflowDefinitionId); + + if (processDefinition == null) + { + return null; + } + else + { + String processName = processDefinition.getName(); + if (tenantService.isEnabled()) + { + tenantService.checkDomain(processName); // throws + // exception + // if domain + // mismatch + } + + TaskMgmtDefinition taskMgmtDef = processDefinition.getTaskMgmtDefinition(); + List workflowTaskDefs = new ArrayList(); + for (Object task : taskMgmtDef.getTasks().values()) + { + workflowTaskDefs.add(createWorkflowTaskDefinition((Task)task)); + } + return (workflowTaskDefs.size() == 0) ? null : workflowTaskDefs; + } + } + }); + } + catch(JbpmException e) + { + String msg = messageService.getMessage(ERR_GET_TASK_DEFS, workflowDefinitionId); + throw new WorkflowException(msg, e); + } + } + + /** + * Gets a jBPM process definition + * + * @param graphSession + * jBPM graph session + * @param workflowDefinitionId + * workflow definition id + * @return process definition + */ + protected ProcessDefinition getProcessDefinition(GraphSession graphSession, String workflowDefinitionId) + { + ProcessDefinition processDefinition = graphSession.getProcessDefinition(getJbpmId(workflowDefinitionId)); + + if ((processDefinition != null) && (tenantService.isEnabled())) + { + try + { + tenantService.checkDomain(processDefinition.getName()); // throws + // exception + // if + // domain + // mismatch + } + catch (RuntimeException re) + { + processDefinition = null; + } + } + + if (processDefinition == null) + { + String msg = messageService.getMessage(ERR_GET_PROCESS_DEF, workflowDefinitionId); + throw new WorkflowException(msg); + } + return processDefinition; + } + + + // + // Workflow Instance Management... + // + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.WorkflowComponent#startWorkflow(java.lang. + * String, java.util.Map) + */ + public WorkflowPath startWorkflow(final String workflowDefinitionId, final Map parameters) + { + try + { + return (WorkflowPath) jbpmTemplate.execute(new JbpmCallback() + { + @SuppressWarnings("synthetic-access") + public Object doInJbpm(JbpmContext context) + { + // initialise jBPM actor (for any processes that wish to + // record the initiator) + String currentUserName = AuthenticationUtil.getFullyAuthenticatedUser(); + context.setActorId(currentUserName); + + // construct a new process + GraphSession graphSession = context.getGraphSession(); + ProcessDefinition processDefinition = getProcessDefinition(graphSession, workflowDefinitionId); + ProcessInstance processInstance = new ProcessInstance(processDefinition); + processInstance.setKey(GUID.generate()); + + // assign initial process context + ContextInstance processContext = processInstance.getContextInstance(); + processContext.setVariable(WorkflowConstants.PROP_CANCELLED, false); + if(parameters!=null) + { + Serializable packageNode = parameters.get(WorkflowModel.ASSOC_PACKAGE); + if (packageNode != null) + { + String pckgName = factory.mapQNameToName(WorkflowModel.ASSOC_PACKAGE); + processContext.setVariable(pckgName, new JBPMNode((NodeRef) packageNode, serviceRegistry)); + } + } + NodeRef companyHome = getCompanyHome(); + processContext.setVariable(WorkflowConstants.PROP_COMPANY_HOME, new JBPMNode(companyHome, serviceRegistry)); + NodeRef initiatorPerson = mapNameToPerson(currentUserName); + if (initiatorPerson != null) + { + processContext.setVariable(WorkflowConstants.PROP_INITIATOR, new JBPMNode(initiatorPerson, serviceRegistry)); + NodeRef initiatorHome = (NodeRef) nodeService.getProperty(initiatorPerson, + ContentModel.PROP_HOMEFOLDER); + if (initiatorHome != null) + { + processContext.setVariable(WorkflowConstants.PROP_INITIATOR_HOME, new JBPMNode(initiatorHome, serviceRegistry)); + } + } + processContext.setVariable(WorkflowConstants.PROP_WORKFLOW_INSTANCE_ID, createGlobalId(new Long(processInstance.getId()) + .toString())); + + // create the start task if one exists + Token token = processInstance.getRootToken(); + Task startTask = processInstance.getTaskMgmtInstance().getTaskMgmtDefinition().getStartTask(); + if (startTask != null) + { + TaskInstance taskInstance = processInstance.getTaskMgmtInstance().createStartTaskInstance(); + setTaskProperties(taskInstance, parameters); + token = taskInstance.getToken(); + } + + // Save the process instance along with the task instance + context.save(processInstance); + return createWorkflowPath(token); + } + }); + } + catch(JbpmException e) + { + throw getStartWorkflowException(workflowDefinitionId, e); + } + catch (DataAccessException e) + { + throw getStartWorkflowException(workflowDefinitionId, e); + } + } + + private WorkflowException getStartWorkflowException(final String workflowDefinitionId, Exception e) + { + String msg = messageService.getMessage(ERR_START_WORKFLOW, workflowDefinitionId); + return new WorkflowException(msg, e); + } + + /** + * {@inheritDoc} + */ + public List getActiveWorkflows() + { + return getWorkflowsInternal(null, true); + } + + /** + * {@inheritDoc} + */ + @Override + public List getCompletedWorkflows() + { + return getWorkflowsInternal(null, false); + } + + /** + * {@inheritDoc} + */ + @Override + public List getWorkflows() + { + return getWorkflowsInternal(null, null); + } + + /** + * {@inheritDoc} + */ + public List getActiveWorkflows(final String workflowDefinitionId) + { + return getWorkflowsInternal(workflowDefinitionId, true); + } + + /** + * {@inheritDoc} + */ + public List getCompletedWorkflows(final String workflowDefinitionId) + { + return getWorkflowsInternal(workflowDefinitionId, false); + } + + /** + * {@inheritDoc} + */ + public List getWorkflows(final String workflowDefinitionId) + { + return getWorkflowsInternal(workflowDefinitionId, null); + } + + private List getWorkflowsInternal(String workflowDefinitionId, Boolean active) + { + try + { + final Long processDefId = workflowDefinitionId == null ? null : getJbpmId(workflowDefinitionId); + List instances = getProcessInstances(processDefId, active); + return convertWorkflows(instances); + } + catch(JbpmException e) + { + String msg = messageService.getMessage(ERR_GET_ACTIVE_WORKFLOW_INSTS, workflowDefinitionId); + throw new WorkflowException(msg, e); + } + } + + @SuppressWarnings("unchecked") + private List getProcessInstances(final Long processDefId, final Boolean active) + { + return (List) jbpmTemplate.execute(new JbpmCallback() + { + public Object doInJbpm(JbpmContext context) + { + Session session = context.getSession(); + Criteria criteria = session.createCriteria(ProcessInstance.class); + if(processDefId!=null) + { + Criteria definitionCriteria = criteria.createCriteria("processDefinition"); + definitionCriteria.add(Restrictions.eq("id", processDefId)); + } + if(Boolean.TRUE.equals(active)) + { + criteria.add(Restrictions.isNull("end")); + } + else if(Boolean.FALSE.equals(active)) + { + criteria.add(Restrictions.isNotNull("end")); + } + return criteria.list(); + } + }); + } + + /** + * {@inheritDoc} + */ + public WorkflowInstance getWorkflowById(final String workflowId) + { + try + { + return (WorkflowInstance) jbpmTemplate.execute(new JbpmCallback() + { + public Object doInJbpm(JbpmContext context) + { + // retrieve workflow + GraphSession graphSession = context.getGraphSession(); + ProcessInstance processInstance = getProcessInstanceIfExists(graphSession, workflowId); + return createWorkflowInstance(processInstance); + } + }); + } + catch(JbpmException e) + { + String msg = messageService.getMessage(ERR_GET_WORKFLOW_INST_BY_ID); + throw new WorkflowException(msg, e); + } + } + + private ProcessInstance getProcessInstanceIfExists(GraphSession graphSession, String workflowId) + { + ProcessInstance processInstance = graphSession.getProcessInstance(getJbpmId(workflowId)); + if ((processInstance != null) && (tenantService.isEnabled())) + { + try + { + tenantService.checkDomain(processInstance.getProcessDefinition().getName()); // throws + // exception + // if + // domain + // mismatch + } + catch (RuntimeException re) + { + processInstance = null; + } + } + return processInstance; + } + + /** + * Gets a jBPM Process Instance + * + * @param graphSession + * jBPM graph session + * @param workflowId + * workflow id + * @return process instance + */ + protected ProcessInstance getProcessInstance(GraphSession graphSession, String workflowId) + { + ProcessInstance processInstance = getProcessInstanceIfExists(graphSession, workflowId); + if (processInstance == null) + { + String msg = messageService.getMessage(ERR_GET_PROCESS_INSTANCE, workflowId); + throw new WorkflowException(msg); + } + return processInstance; + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.WorkflowComponent#getWorkflowPaths(java.lang + * .String) + */ + @SuppressWarnings("unchecked") + public List getWorkflowPaths(final String workflowId) + { + try + { + return (List) jbpmTemplate.execute(new JbpmCallback() + { + public Object doInJbpm(JbpmContext context) + { + // retrieve process instance + GraphSession graphSession = context.getGraphSession(); + ProcessInstance processInstance = getProcessInstance(graphSession, workflowId); + + // convert jBPM tokens to workflow posisitons + List tokens = processInstance.findAllTokens(); + List paths = new ArrayList(tokens.size()); + for (Token token : tokens) + { + if (!token.hasEnded()) + { + WorkflowPath path = createWorkflowPath(token); + paths.add(path); + } + } + + return paths; + } + }); + } + catch(JbpmException e) + { + String msg = messageService.getMessage(ERR_GET_WORKFLOW_PATHS, workflowId); + throw new WorkflowException(msg, e); + } + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.WorkflowComponent#getPathProperties(java.lang + * .String) + */ + @SuppressWarnings("unchecked") + public Map getPathProperties(final String pathId) + { + try + { + return (Map) jbpmTemplate.execute(new JbpmCallback() + { + public Map doInJbpm(JbpmContext context) + { + // retrieve jBPM token for workflow position + GraphSession graphSession = context.getGraphSession(); + Token token = getWorkflowToken(graphSession, pathId); + ContextInstance instanceContext = token.getProcessInstance().getContextInstance(); + Map properties = new HashMap(10); + while (token != null) + { + + TokenVariableMap varMap = instanceContext.getTokenVariableMap(token); + if (varMap != null) + { + Map tokenVars = varMap.getVariablesLocally(); + for (Map.Entry entry : tokenVars.entrySet()) + { + String key = entry.getKey(); + QName qname = factory.mapNameToQName(key); + + if (!properties.containsKey(qname)) + { + Serializable value = convertValue(entry.getValue()); + properties.put(qname, value); + } + } + } + token = token.getParent(); + } + + return properties; + } + }); + } + catch(JbpmException e) + { + String msg = messageService.getMessage(ERR_GET_PATH_PROPERTIES, pathId); + throw new WorkflowException(msg, e); + } + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.WorkflowComponent#cancelWorkflow(java.lang + * .String) + */ + public WorkflowInstance cancelWorkflow(final String workflowId) + { + return cancelWorkflows(Collections.singletonList(workflowId)).get(0); + } + + /* (non-Javadoc) + * @see org.alfresco.repo.workflow.WorkflowComponent#cancelWorkflows + */ + @SuppressWarnings("unchecked") + public List cancelWorkflows(final ListworkflowIds) + { + return (List) jbpmTemplate.execute(new JbpmCallback() + { + public Object doInJbpm(JbpmContext context) + { + // Bypass the cache making sure not to flush it + Session session = context.getSession(); + CacheMode cacheMode = session.getCacheMode(); + FlushMode flushMode = session.getFlushMode(); + session.setCacheMode(CacheMode.GET); + session.setFlushMode(FlushMode.MANUAL); + try + { + List workflowInstances = new ArrayList(workflowIds.size()); + Map processInstances = new HashMap(workflowIds.size() * 2); + GraphSession graphSession = context.getGraphSession(); + + // retrieve and cancel process instances + for (String workflowId : workflowIds) + { + try + { + ProcessInstance processInstance = getProcessInstance(graphSession, workflowId); + processInstance.getContextInstance().setVariable("cancelled", true); + processInstance.end(); + processInstances.put(workflowId, processInstance); + } + catch(JbpmException e) + { + String msg = messageService.getMessage(ERR_CANCEL_WORKFLOW, workflowId); + throw new WorkflowException(msg, JbpmAccessor.convertJbpmException(e)); + } + } + + // Flush at the end of the batch + session.flush(); + + for (String workflowId : workflowIds) + { + try + { + // retrieve process instance + ProcessInstance processInstance = processInstances.get(workflowId); + // TODO: Determine if this is the most appropriate way to cancel workflow... + // It might be useful to record point at which it was cancelled etc + workflowInstances.add(createWorkflowInstance(processInstance)); + + // delete the process instance + graphSession.deleteProcessInstance(processInstance, true, true); + } + catch(JbpmException e) + { + String msg = messageService.getMessage(ERR_CANCEL_WORKFLOW, workflowId); + throw new WorkflowException(msg, JbpmAccessor.convertJbpmException(e)); + } + } + + // Flush at the end of the batch + session.flush(); + return workflowInstances; + } + finally + { + session.setCacheMode(cacheMode); + session.setFlushMode(flushMode); + } + } + }); + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.WorkflowComponent#cancelWorkflow(java.lang + * .String) + */ + public WorkflowInstance deleteWorkflow(final String workflowId) + { + try + { + return (WorkflowInstance) jbpmTemplate.execute(new JbpmCallback() + { + public Object doInJbpm(JbpmContext context) + { + // retrieve and cancel process instance + GraphSession graphSession = context.getGraphSession(); + ProcessInstance processInstance = getProcessInstance(graphSession, workflowId); + + // delete the process instance + graphSession.deleteProcessInstance(processInstance, true, true); + Date endDate = new Date(); + WorkflowInstance workflowInstance = createWorkflowInstance(processInstance, endDate); + return workflowInstance; + } + }); + } + catch(JbpmException e) + { + String msg = messageService.getMessage(ERR_DELETE_WORKFLOW, workflowId); + throw new WorkflowException(msg, e); + } + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.WorkflowComponent#signal(java.lang.String, + * java.lang.String) + */ + public WorkflowPath signal(final String pathId, final String transition) + { + try + { + return (WorkflowPath) jbpmTemplate.execute(new JbpmCallback() + { + public Object doInJbpm(JbpmContext context) + { + // retrieve jBPM token for workflow position + GraphSession graphSession = context.getGraphSession(); + Token token = getWorkflowToken(graphSession, pathId); + + // signal the transition + if (transition == null) + { + token.signal(); + } + else + { + Node node = token.getNode(); + if (!node.hasLeavingTransition(transition)) + { + throw new WorkflowException("Transition '" + transition + + "' is invalid for Workflow path '" + pathId + "'"); + } + token.signal(transition); + } + + // save + ProcessInstance processInstance = token.getProcessInstance(); + context.save(processInstance); + + // return new workflow path + return createWorkflowPath(token); + } + }); + } + catch(JbpmException e) + { + String msg = messageService.getMessage(ERR_SIGNAL_TRANSITION, transition, pathId); + throw new WorkflowException(msg, e); + } + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.WorkflowComponent#fireEvent(java.lang.String, + * java.lang.String) + */ + public WorkflowPath fireEvent(final String pathId, final String event) + { + try + { + return (WorkflowPath) jbpmTemplate.execute(new JbpmCallback() + { + @SuppressWarnings("unchecked") + public Object doInJbpm(JbpmContext context) + { + // NOTE: Do not allow jBPM built-in events to be fired + if (event.equals(Event.EVENTTYPE_AFTER_SIGNAL) || event.equals(Event.EVENTTYPE_BEFORE_SIGNAL) + || event.equals(Event.EVENTTYPE_NODE_ENTER) || event.equals(Event.EVENTTYPE_NODE_LEAVE) + || event.equals(Event.EVENTTYPE_PROCESS_END) + || event.equals(Event.EVENTTYPE_PROCESS_START) + || event.equals(Event.EVENTTYPE_SUBPROCESS_CREATED) + || event.equals(Event.EVENTTYPE_SUBPROCESS_END) + || event.equals(Event.EVENTTYPE_SUPERSTATE_ENTER) + || event.equals(Event.EVENTTYPE_SUPERSTATE_LEAVE) + || event.equals(Event.EVENTTYPE_TASK_ASSIGN) + || event.equals(Event.EVENTTYPE_TASK_CREATE) || event.equals(Event.EVENTTYPE_TASK_END) + || event.equals(Event.EVENTTYPE_TASK_START) || event.equals(Event.EVENTTYPE_TIMER) + || event.equals(Event.EVENTTYPE_TRANSITION)) + { + String msg = messageService.getMessage(ERR_INVALID_EVENT, event); + throw new WorkflowException(msg); + } + + // retrieve jBPM token for workflow position + GraphSession graphSession = context.getGraphSession(); + Token token = getWorkflowToken(graphSession, pathId); + + ExecutionContext executionContext = new ExecutionContext(token); + TaskMgmtSession taskSession = context.getTaskMgmtSession(); + List tasks = taskSession.findTaskInstancesByToken(token.getId()); + if (tasks.size() == 0) + { + // fire the event against current node for the token + Node node = token.getNode(); + node.fireEvent(event, executionContext); + } + else + { + // fire the event against tasks associated with the node + // NOTE: this will also propagate the event to the node + for (TaskInstance task : tasks) + { + executionContext.setTaskInstance(task); + task.getTask().fireEvent(event, executionContext); + } + } + + // save + ProcessInstance processInstance = token.getProcessInstance(); + context.save(processInstance); + + // return new workflow path + return createWorkflowPath(token); + } + }); + } + catch(JbpmException e) + { + String msg = messageService.getMessage(ERR_FIRE_EVENT, event, pathId); + throw new WorkflowException(msg, e); + } + } + + /** + * {@inheritDoc} + */ + public List getTasksForWorkflowPath(String pathId) + { + return getTasksForWorkflowPath(pathId, false); + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.WorkflowComponent#getTasksForWorkflowPath( + * java.lang.String) + */ + @SuppressWarnings("unchecked") + public List getTasksForWorkflowPath(final String pathId, final boolean sameSession) + { + try + { + return (List) jbpmTemplate.execute(new JbpmCallback() + { + public List doInJbpm(JbpmContext context) + { + // retrieve tasks at specified workflow path + GraphSession graphSession = context.getGraphSession(); + Token token = getWorkflowToken(graphSession, pathId); + TaskMgmtSession taskSession = context.getTaskMgmtSession(); + List tasks = taskSession.findTaskInstancesByToken(token.getId()); + return getWorkflowTasks(tasks, sameSession); + } + }); + } + catch(JbpmException e) + { + String msg = messageService.getMessage(ERR_GET_TASKS_FOR_PATH, pathId); + throw new WorkflowException(msg, e); + } + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.WorkflowComponent#getTimers(java.lang.String) + */ + @SuppressWarnings("unchecked") + public List getTimers(final String workflowId) + { + try + { + return (List) jbpmTemplate.execute(new JbpmCallback() + { + public List doInJbpm(JbpmContext context) + { + // retrieve process + GraphSession graphSession = context.getGraphSession(); + ProcessInstance process = getProcessInstance(graphSession, workflowId); + + // retrieve timers for process + Session session = context.getSession(); + Query query = session.createQuery(PROCESS_TIMERS_QUERY); + query.setEntity("process", process); + List timers = query.list(); + + // convert timers to appropriate service response format + List workflowTimers = new ArrayList(timers.size()); + for (Timer timer : timers) + { + WorkflowTimer workflowTimer = createWorkflowTimer(timer); + workflowTimers.add(workflowTimer); + } + return workflowTimers; + } + }); + } + catch(JbpmException e) + { + String msg = messageService.getMessage(ERR_GET_TIMERS, workflowId); + throw new WorkflowException(msg, e); + } + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.repo.workflow.WorkflowComponent#hasWorkflowImage(java.lang.String) + */ + public boolean hasWorkflowImage(final String workflowInstanceId) + { + // we don't support workflow instance diagrams in JBPM so return false + return false; + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.repo.workflow.WorkflowComponent#getWorkflowImage(java.lang.String) + */ + public InputStream getWorkflowImage(final String workflowInstanceId) + { + // we don't support workflow instance diagrams in JBPM so return null + return null; + } + + // + // Task Management ... + // + + /** + * {@inheritDoc} + */ + public List getAssignedTasks(String authority, WorkflowTaskState state) + { + return getAssignedTasks(authority, state, false); + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.TaskComponent#getAssignedTasks(java.lang.String + * , org.alfresco.service.cmr.workflow.WorkflowTaskState) + */ + @SuppressWarnings("unchecked") + public List getAssignedTasks(final String authority, final WorkflowTaskState state, final boolean sameSession) + { + try + { + return (List) jbpmTemplate.execute(new JbpmCallback() + { + public List doInJbpm(JbpmContext context) + { + // retrieve tasks assigned to authority + List tasks; + if (state.equals(WorkflowTaskState.IN_PROGRESS)) + { + return findActiveTaskInstances(authority, context); + } + else + { + // Note: This method is not implemented by jBPM + tasks = findCompletedTaskInstances(context, authority); + return getWorkflowTasks(tasks, sameSession); + } + + } + }); + } + catch(JbpmException e) + { + String msg = messageService.getMessage(ERR_GET_ASSIGNED_TASKS, authority, state); + throw new WorkflowException(msg, e); + } + } + + /** + * Gets the completed task list for the specified actor + * + * @param jbpmContext the jbpm context + * @param actorId the actor to retrieve tasks for + * @return the tasks + */ + @SuppressWarnings("unchecked") + private List findCompletedTaskInstances(JbpmContext jbpmContext, String actorId) + { + List result = null; + try + { + Session session = jbpmContext.getSession(); + Query query = session.createQuery(COMPLETED_TASKS_QUERY); + query.setString("actorId", actorId); + result = query.list(); + } + catch (Exception e) + { + String msg = messageService.getMessage(ERR_FIND_COMPLETED_TASK_INSTS, actorId); + throw new JbpmException(msg, e); + } + return result; + } + + @SuppressWarnings("unchecked") + private List findActiveTaskInstances(final String authority, JbpmContext context) + { + Session session = context.getSession(); + Query query = session.getNamedQuery("org.alfresco.repo.workflow.findTaskInstancesByActorId"); + query.setString("actorId", authority); + query.setBoolean("true", true); + List workflowTasks = getWorkflowTasks(session, query.list()); + // Do we need to clear a session here? It takes 3 seconds with 2000 workflows. + // session.clear(); + return workflowTasks; + } + + protected List getWorkflowTasks(Session session, List rows) + { + List workflowTasks = new ArrayList(rows.size()); + + /// ------------------------ + + // Preload data into L1 session + List taskInstanceIds = new ArrayList(rows.size()); + List contextInstanceIds = new ArrayList(rows.size()); + for (Object[] row : rows) + { + TaskInstance ti = (TaskInstance) row[0]; + taskInstanceIds.add(ti.getId()); + ContextInstance ci = (ContextInstance) row[8]; + contextInstanceIds.add(ci.getId()); + } + Map taskInstanceCache = new HashMap(rows.size()); + if (taskInstanceIds.size() > 0) + { + taskInstanceCache = cacheTasks(session, taskInstanceIds); + } + Map variablesCache = new HashMap(rows.size()); + if (contextInstanceIds.size() > 0) + { + variablesCache = cacheVariables(session, contextInstanceIds); + } + taskInstanceIds.clear(); + contextInstanceIds.clear(); + /// ------------------------ + for(Object[] row : rows) + { + WorkflowTask workflowTask = makeWorkflowTask(row, taskInstanceCache, variablesCache); + if(workflowTask !=null ) + { + workflowTasks.add(workflowTask); + } + } + return workflowTasks; + } + + private WorkflowTask makeWorkflowTask(Object[] row, Map taskInstanceCache, Map variablesCache) + { + TaskInstance ti = (TaskInstance) row[0]; + Token token = (Token)row[2]; + ProcessInstance processInstance = (ProcessInstance)row[3]; + Node node = (Node)row[4]; + Task task = (Task)row[5]; + ProcessDefinition processDefinition = (ProcessDefinition)row[6]; + Task startTask = (Task)row[7]; + ContextInstance contextInstance = (ContextInstance) row[8]; + + if (tenantService.isEnabled()) + { + try + { + tenantService.checkDomain(processDefinition.getName()); + } + catch (RuntimeException re) + { + // deliberately skip this one - due to domain mismatch - eg. when querying by group authority + return null; + } + } + // TaskInstance with some precached properties + TaskInstance helperTi = taskInstanceCache.get(ti.getId()); + + @SuppressWarnings("unchecked") + Map variables = variablesCache.get(contextInstance.getId()).getVariables(); + // WorkflowTaskProperies + Map properties = getTaskProperties(helperTi != null ? helperTi : ti, false, variablesCache); + + WorkflowDefinition wfDef = createWorkflowDefinition(processDefinition, startTask); + WorkflowInstance instance = createWorkflowInstance(processInstance, wfDef, null, variables); + WorkflowNode wfNode = createWorkflowNode(node); + WorkflowPath path = createWorkflowPath(token, instance, wfNode); + WorkflowTaskDefinition taskDef = createWorkflowTaskDefinition(task); + return createWorkflowTask(ti, taskDef, path, properties); + } + + private Map cacheVariables(Session session, List ids) + { + // Preload data into L1 session + int batchSize = 800; // Must limit IN clause size! + List batch = new ArrayList(ids.size()); + Map cachedResults = new HashMap(); + for (Long id : ids) + { + batch.add(id); + if (batch.size() >= batchSize) + { + cacheVariablesNoBatch(session, batch, cachedResults); + batch.clear(); + } + } + if (batch.size() > 0) + { + cacheVariablesNoBatch(session, batch, cachedResults); + } + batch.clear(); + return cachedResults; + } + + @SuppressWarnings({ "unchecked", "cast" }) + private void cacheVariablesNoBatch(Session session, List contextInstanceIds, Map variablesCache) + { + Query query = session.getNamedQuery("org.alfresco.repo.workflow.cacheInstanceVariables"); + query.setParameterList("ids", contextInstanceIds); + query.setCacheMode(CacheMode.PUT); + query.setFlushMode(FlushMode.MANUAL); + query.setCacheable(true); + + List results = (List) query.list(); + for (TokenVariableMap tokenVariableMap : results) + { + variablesCache.put(tokenVariableMap.getContextInstance().getId(), tokenVariableMap); + } + } + + private Map cacheTasks(Session session, List ids) + { + // Preload data into L1 session + int batchSize = 800; // Must limit IN clause size! + List batch = new ArrayList(ids.size()); + Map cachedResults = new HashMap(); + for (Long id : ids) + { + batch.add(id); + if (batch.size() >= batchSize) + { + cacheTasksNoBatch(session, batch, cachedResults); + batch.clear(); + } + } + if (batch.size() > 0) + { + cacheTasksNoBatch(session, batch, cachedResults); + } + batch.clear(); + return cachedResults; + } + + @SuppressWarnings({ "unchecked", "cast" }) + private void cacheTasksNoBatch(Session session, List taskInstanceIds, Map returnMap) + { + Query query = session.getNamedQuery("org.alfresco.repo.workflow.cacheTaskInstanceProperties"); + query.setParameterList("ids", taskInstanceIds); + query.setCacheMode(CacheMode.PUT); + query.setFlushMode(FlushMode.MANUAL); + query.setCacheable(true); + + List results = (List) query.list(); + for (TaskInstance taskInstance : results) + { + returnMap.put(taskInstance.getId(), taskInstance); + } + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.TaskComponent#getPooledTasks(java.util.List) + */ + @SuppressWarnings("unchecked") + public List getPooledTasks(final List authorities) + { + try + { + return (List) jbpmTemplate.execute(new JbpmCallback() + { + public List doInJbpm(JbpmContext context) + { + // retrieve pooled tasks for all flattened authorities + TaskMgmtSession taskSession = context.getTaskMgmtSession(); + List tasks = taskSession.findPooledTaskInstances(authorities); + return getWorkflowTasks(tasks); + } + }); + } + catch(JbpmException e) + { + String msg = messageService.getMessage(ERR_GET_POOLED_TASKS, authorities); + throw new WorkflowException(msg, e); + } + } + + + /* (non-Javadoc) + * @see org.alfresco.repo.workflow.TaskComponent#queryTasks(org.alfresco.service.cmr.workflow.WorkflowTaskQuery) + */ + @Override + public List queryTasks(WorkflowTaskQuery query) + { + return queryTasks(query, false); + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.TaskComponent#queryTasks(org.alfresco.service + * .cmr.workflow.WorkflowTaskFilter, boolean) + */ + @SuppressWarnings("unchecked") + public List queryTasks(final WorkflowTaskQuery query, final boolean sameSession) + { + try + { + return (List) jbpmTemplate.execute(new JbpmCallback() + { + public List doInJbpm(JbpmContext context) + { + // Bypass the cache making sure not to flush it + Session session = context.getSession(); + CacheMode cacheMode = session.getCacheMode(); + try + { + session.setCacheMode(CacheMode.GET); + Criteria criteria = createTaskQueryCriteria(session, query); + List tasks = criteria.list(); + return getWorkflowTasks(tasks, sameSession); + } + finally + { + session.setCacheMode(cacheMode); + } + } + }); + } + catch(JbpmException e) + { + String msg = messageService.getMessage(ERR_QUERY_TASKS, query); + throw new WorkflowException(msg, e); + } + } + + protected List getWorkflowTasks(List tasks) + { + return getWorkflowTasks(tasks, false); + } + + protected List getWorkflowTasks(List tasks, boolean sameSession) + { + final List filteredTasks; + if (tenantService.isEnabled()) + { + filteredTasks = new ArrayList(tasks.size()); + for (TaskInstance task : tasks) + { + try + { + tenantService.checkDomain(task.getTask().getProcessDefinition().getName()); + filteredTasks.add(task); + } + catch (RuntimeException re) + { + // deliberately skip this one - due to domain mismatch - eg. + // when querying by group authority + continue; + } + } + } + else + { + filteredTasks = tasks; + } + + List workflowTasks; + if (sameSession) + { + workflowTasks = new AbstractList() + { + @Override + public WorkflowTask get(int index) + { + TaskInstance task = filteredTasks.get(index); + return createWorkflowTask(task); + } + + @Override + public int size() + { + return filteredTasks.size(); + } + }; + } + else + { + workflowTasks = new ArrayList(filteredTasks.size()); + for (TaskInstance task : filteredTasks) + { + WorkflowTask workflowTask = createWorkflowTask(task); + workflowTasks.add(workflowTask); + } + } + + return workflowTasks; + } + + /** + * Construct a JBPM Hibernate query based on the Task Query provided + * + * @param session + * @param query + * @return jbpm hiberate query criteria + */ + private Criteria createTaskQueryCriteria(Session session, WorkflowTaskQuery query) + { + Criteria task = session.createCriteria(TaskInstance.class); + + // task id + if (query.getTaskId() != null) + { + task.add(Restrictions.eq("id", getJbpmId(query.getTaskId()))); + } + + // task state + if (query.getTaskState() != null) + { + WorkflowTaskState state = query.getTaskState(); + if (state == WorkflowTaskState.IN_PROGRESS) + { + task.add(Restrictions.eq("isOpen", true)); + task.add(Restrictions.isNull("end")); + } + else if (state == WorkflowTaskState.COMPLETED) + { + task.add(Restrictions.eq("isOpen", false)); + task.add(Restrictions.isNotNull("end")); + } + } + + // task name + if (query.getTaskName() != null) + { + task.add(Restrictions.eq("name", query.getTaskName().toPrefixString(namespaceService))); + } + + // task actor + if (query.getActorId() != null) + { + task.add(Restrictions.eq("actorId", query.getActorId())); + } + + // task custom properties + if (query.getTaskCustomProps() != null) + { + Map props = query.getTaskCustomProps(); + if (props.size() > 0) + { + Criteria variables = task.createCriteria("variableInstances"); + Disjunction values = Restrictions.disjunction(); + for (Map.Entry prop : props.entrySet()) + { + Conjunction value = Restrictions.conjunction(); + value.add(Restrictions.eq("name", factory.mapQNameToName(prop.getKey()))); + value.add(Restrictions.eq("value", prop.getValue().toString())); + values.add(value); + } + variables.add(values); + } + } + + // process criteria + Criteria process = createProcessCriteria(task, query); + + // process custom properties + if (query.getProcessCustomProps() != null) + { + // TODO: Due to Hibernate bug + // http://opensource.atlassian.com/projects/hibernate/browse/HHH-957 + // it's not possible to perform a sub-select with the criteria api. + // For now issue a + // secondary query and create an IN clause. + Map props = query.getProcessCustomProps(); + if (props.size() > 0) + { + // create criteria for process variables + Criteria variables = session.createCriteria(VariableInstance.class); + variables.setProjection(Projections.distinct(Property.forName("processInstance"))); + Disjunction values = Restrictions.disjunction(); + for (Map.Entry prop : props.entrySet()) + { + Conjunction value = Restrictions.conjunction(); + value.add(Restrictions.eq("name", factory.mapQNameToName(prop.getKey()))); + value.add(Restrictions.eq("value", prop.getValue().toString())); + values.add(value); + } + variables.add(values); + + // note: constrain process variables to same criteria as tasks + createProcessCriteria(variables, query); + + Disjunction processIdCriteria = createProcessIdCriteria(variables); + + // constrain tasks by process list + process = (process == null) ? task.createCriteria("processInstance") : process; + process.add(processIdCriteria); + } + } + + // order by + if (query.getOrderBy() != null) + { + WorkflowTaskQuery.OrderBy[] orderBy = query.getOrderBy(); + for (WorkflowTaskQuery.OrderBy orderByPart : orderBy) + { + if (orderByPart == WorkflowTaskQuery.OrderBy.TaskActor_Asc) + { + task.addOrder(Order.asc("actorId")); + } + else if (orderByPart == WorkflowTaskQuery.OrderBy.TaskActor_Desc) + { + task.addOrder(Order.desc("actorId")); + } + else if (orderByPart == WorkflowTaskQuery.OrderBy.TaskCreated_Asc) + { + task.addOrder(Order.asc("create")); + } + else if (orderByPart == WorkflowTaskQuery.OrderBy.TaskCreated_Desc) + { + task.addOrder(Order.desc("create")); + } + else if (orderByPart == WorkflowTaskQuery.OrderBy.TaskDue_Asc) + { + task.addOrder(Order.asc("dueDate")); + } + else if (orderByPart == WorkflowTaskQuery.OrderBy.TaskDue_Desc) + { + task.addOrder(Order.desc("dueDate")); + } + else if (orderByPart == WorkflowTaskQuery.OrderBy.TaskId_Asc) + { + task.addOrder(Order.asc("id")); + } + else if (orderByPart == WorkflowTaskQuery.OrderBy.TaskId_Desc) + { + task.addOrder(Order.desc("id")); + } + else if (orderByPart == WorkflowTaskQuery.OrderBy.TaskName_Asc) + { + task.addOrder(Order.asc("name")); + } + else if (orderByPart == WorkflowTaskQuery.OrderBy.TaskName_Desc) + { + task.addOrder(Order.desc("name")); + } + else if (orderByPart == WorkflowTaskQuery.OrderBy.TaskState_Asc) + { + task.addOrder(Order.asc("end")); + } + else if (orderByPart == WorkflowTaskQuery.OrderBy.TaskState_Desc) + { + task.addOrder(Order.desc("end")); + } + } + } + + // limit results + if (query.getLimit() != -1) + { + task.setMaxResults(query.getLimit()); + } + + return task; + } + + /** + * @param variables + * @return + */ + private Disjunction createProcessIdCriteria(Criteria variables) + { + // retrieve list of processes matching specified variables + List processList = variables.list(); + Object[] processIds = getProcessIds( processList); + + // ALF-5841 fix + int batch = 0; + List buf = new ArrayList(1000); + + Disjunction ids = Restrictions.disjunction(); + for (Object id : processIds) + { + if (batch < 1000) + { + batch++; + buf.add(id); + } + else + { + ids.add(Restrictions.in("id", buf)); + batch = 0; + buf.clear(); + } + } + + if (!buf.isEmpty()) + { + ids.add(Restrictions.in("id", buf)); + } + return ids; + } + + private Object[] getProcessIds(List processList) + { + ArrayList ids = new ArrayList(processList.size()); + if (processList.isEmpty()) + { + ids.add(new Long(-1)); + } + else + { + for (Object obj : processList) + { + ProcessInstance instance = (ProcessInstance) obj; + ids.add(instance.getId()); + } + } + return ids.toArray(); + } + + /** + * Create process-specific query criteria + * + * @param root + * @param query + * @return + */ + private Criteria createProcessCriteria(Criteria root, WorkflowTaskQuery query) + { + Criteria process = null; + + // process active? + if (query.isActive() != null) + { + process = root.createCriteria("processInstance"); + if (query.isActive()) + { + process.add(Restrictions.isNull("end")); + } + else + { + process.add(Restrictions.isNotNull("end")); + } + } + + // process id + if (query.getProcessId() != null) + { + process = (process == null) ? root.createCriteria("processInstance") : process; + process.add(Restrictions.eq("id", getJbpmId(query.getProcessId()))); + } + + // process definition name + String definitionName = query.getWorkflowDefinitionName(); + if(definitionName!=null) + { + definitionName = createLocalId(definitionName); + } + if(definitionName == null) + { + QName qName = query.getProcessName(); + definitionName= qName == null ? null : qName.toPrefixString(namespaceService); + } + if (definitionName != null) + { + process = (process == null) ? root.createCriteria("processInstance") : process; + Criteria processDef = process.createCriteria("processDefinition"); + String processName = tenantService.getName(definitionName); + processDef.add(Restrictions.eq("name", processName)); + } + + return process; + } + + /** + * Gets a jBPM Task Instance + * + * @param taskSession + * jBPM task session + * @param taskId + * task id + * @return task instance + */ + protected TaskInstance getTaskInstance(TaskMgmtSession taskSession, String taskId) + { + TaskInstance taskInstance = taskSession.getTaskInstance(getJbpmId(taskId)); + + if ((taskInstance != null) && (tenantService.isEnabled())) + { + try + { + // throws exception if domain mismatch + tenantService.checkDomain(taskInstance.getTask().getProcessDefinition().getName()); + } + catch (RuntimeException re) + { + taskInstance = null; + } + } + + if (taskInstance == null) + { + String msg = messageService.getMessage(ERR_GET_TASK_INST, taskId); + throw new WorkflowException(msg); + } + return taskInstance; + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.TaskComponent#updateTask(java.lang.String, + * java.util.Map, java.util.Map, java.util.Map) + */ + public WorkflowTask updateTask(final String taskId, + final Map properties, + final Map> add, final Map> remove) + { + try + { + return (WorkflowTask) jbpmTemplate.execute(new JbpmCallback() + { + @SuppressWarnings("unchecked") + public Object doInJbpm(JbpmContext context) + { + // retrieve task + TaskMgmtSession taskSession = context.getTaskMgmtSession(); + TaskInstance taskInstance = getTaskInstance(taskSession, taskId); + + // create properties to set on task instance + Map newProperties = new HashMap(10); + if(properties!=null) + { + newProperties.putAll(properties); + } + Map existingProperties = getTaskProperties(taskInstance, false); + if (add != null) + { + // add new associations + for (Entry> toAdd : add.entrySet()) + { + // retrieve existing list of noderefs for + // association + QName key = toAdd.getKey(); + Serializable existingValue = newProperties.get(key); + + if (existingValue == null) + { + existingValue = existingProperties.get(key); + } + // make the additions + if (existingValue == null) + { + newProperties.put(key, (Serializable)toAdd.getValue()); + } + else + { + List existingAdd; + if (existingValue instanceof List) + { + existingAdd = (List) existingValue; + } + else + { + existingAdd = new LinkedList(); + existingAdd.add((NodeRef) existingValue); + } + + for (NodeRef nodeRef : toAdd.getValue()) + { + if (!(existingAdd.contains(nodeRef))) + { + existingAdd.add(nodeRef); + } + } + newProperties.put(key, (Serializable) existingAdd); + } + } + } + + if (remove != null) + { + // add new associations + for (Entry> toRemove: remove.entrySet()) + { + // retrieve existing list of noderefs for + // association + QName key = toRemove.getKey(); + Serializable existingValue = newProperties.get(key); + + if (existingValue == null) + { + existingValue = existingProperties.get(key); + } + // make the subtractions + if (existingValue != null) + { + if(existingValue instanceof List) + { + List existingRemove = (List) existingValue; + existingRemove.removeAll(toRemove.getValue()); + newProperties.put(key, (Serializable) existingRemove); + } + else if(toRemove.getValue().contains(existingValue)) + { + newProperties.put(key, new LinkedList()); + } + } + } + } + + // update the task + if (newProperties.isEmpty() == false) + { + setTaskProperties(taskInstance, newProperties); + + // save + ProcessInstance processInstance = taskInstance.getToken().getProcessInstance(); + context.save(processInstance); + } + + // note: the ending of a task may not have signalled (i.e. + // more than one task exists at + // this node) + return createWorkflowTask(taskInstance); + } + }); + } + catch(JbpmException e) + { + String msg = messageService.getMessage(ERR_UPDATE_TASK, taskId); + throw new WorkflowException(msg, e); + } + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.repo.workflow.TaskComponent#startTask(java.lang.String) + */ + public WorkflowTask startTask(String taskId) + { + // TODO: + throw new UnsupportedOperationException(); + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.TaskComponent#suspendTask(java.lang.String) + */ + public WorkflowTask suspendTask(String taskId) + { + // TODO: + throw new UnsupportedOperationException(); + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.repo.workflow.TaskComponent#endTask(java.lang.String, + * java.lang.String) + */ + public WorkflowTask endTask(final String taskId, final String transition) + { + try + { + return (WorkflowTask) jbpmTemplate.execute(new JbpmCallback() + { + public Object doInJbpm(JbpmContext context) + { + // retrieve task + TaskMgmtSession taskSession = context.getTaskMgmtSession(); + TaskInstance taskInstance = getTaskInstance(taskSession, taskId); + + // ensure all mandatory properties have been provided + QName[] missingProps = getMissingMandatoryTaskProperties(taskInstance); + if (missingProps != null && missingProps.length > 0) + { + String props = ""; + for (int i = 0; i < missingProps.length; i++) + { + props += missingProps[i].toString() + ((i < missingProps.length -1) ? "," : ""); + } + String msg = messageService.getMessage(ERR_MANDATORY_TASK_PROPERTIES_MISSING, props); + throw new WorkflowException(msg); + + } + + // signal the transition on the task + if (transition == null) + { + taskInstance.end(); + } + else + { + Node node = taskInstance.getToken().getNode(); + if (node.getLeavingTransition(transition) == null) + { + String msg = messageService.getMessage(ERR_END_TASK_INVALID_TRANSITION, transition, taskId); + throw new WorkflowException(msg); + } + taskInstance.end(transition); + } + + // save + ProcessInstance processInstance = taskInstance.getToken().getProcessInstance(); + context.save(processInstance); + + // note: the ending of a task may not have signalled (i.e. + // more than one task exists at + // this node) + return createWorkflowTask(taskInstance); + } + }); + } + catch(JbpmException e) + { + String msg = messageService.getMessage(ERR_END_TASK, transition, taskId); + throw new WorkflowException(msg, e); + } + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.TaskComponent#getTaskById(java.lang.String) + */ + public WorkflowTask getTaskById(final String taskId) + { + try + { + return (WorkflowTask) jbpmTemplate.execute(new JbpmCallback() + { + public Object doInJbpm(JbpmContext context) + { + // retrieve task + TaskMgmtSession taskSession = context.getTaskMgmtSession(); + TaskInstance taskInstance = getTaskInstance(taskSession, taskId); + return createWorkflowTask(taskInstance); + } + }); + } + catch(JbpmException e) + { + String msg = messageService.getMessage(ERR_GET_TASK_BY_ID, taskId); + throw new WorkflowException(msg, e); + } + } + + @Override + public WorkflowTask getStartTask(final String workflowInstanceId) + { + try + { + return (WorkflowTask) jbpmTemplate.execute(new JbpmCallback() + { + public Object doInJbpm(JbpmContext context) + { + // retrieve process instance + GraphSession graphSession = context.getGraphSession(); + ProcessInstance processInstance = getProcessInstanceIfExists(graphSession, workflowInstanceId); + if(processInstance == null) + { + return null; + } + Task startTask = processInstance.getProcessDefinition().getTaskMgmtDefinition().getStartTask(); + + // retrieve task + Session session = context.getSession(); + Criteria taskCriteria = session.createCriteria(TaskInstance.class); + taskCriteria.add(Restrictions.eq("name", startTask.getName())); + Criteria process = taskCriteria.createCriteria("processInstance"); + process.add(Restrictions.eq("id", processInstance.getId())); + TaskInstance taskInstance = (TaskInstance) taskCriteria.uniqueResult(); + return createWorkflowTask(taskInstance); + } + }); + } + catch (JbpmException e) + { + String msg = messageService.getMessage(ERR_GET_START_TASK, workflowInstanceId); + throw new WorkflowException(msg, e); + } + } + + @SuppressWarnings("unchecked") + public List getStartTasks(final List workflowInstanceIds, final boolean sameSession) + { + try + { + return (List) jbpmTemplate.execute(new JbpmCallback() + { + public Object doInJbpm(JbpmContext context) + { + List jbpmIds = new ArrayList(workflowInstanceIds.size()); + Set startTaskNames = new TreeSet(); + for (String workflowInstanceId : workflowInstanceIds) + { + // retrieve process instance + GraphSession graphSession = context.getGraphSession(); + ProcessInstance processInstance = getProcessInstanceIfExists(graphSession, workflowInstanceId); + if(processInstance != null) + { + jbpmIds.add(processInstance.getId()); + Task startTask = processInstance.getProcessDefinition().getTaskMgmtDefinition().getStartTask(); + startTaskNames.add(startTask.getName()); + } + } + + if (jbpmIds.isEmpty()) + { + return Collections.emptyList(); + } + + // retrieve tasks + Session session = context.getSession(); + Criteria taskCriteria = session.createCriteria(TaskInstance.class); + taskCriteria.add(Restrictions.in("name", startTaskNames)); + Criteria process = taskCriteria.createCriteria("processInstance"); + process.add(Restrictions.in("id", jbpmIds)); + + // Bypass the cache making sure not to flush it + CacheMode cacheMode = session.getCacheMode(); + try + { + session.setCacheMode(CacheMode.GET); + List tasks = process.list(); + return getWorkflowTasks(tasks, sameSession); + } + finally + { + session.setCacheMode(cacheMode); + } + } + }); + } + catch (JbpmException e) + { + String msg = messageService.getMessage(ERR_QUERY_TASKS, workflowInstanceIds); + throw new WorkflowException(msg, e); + } + } + + // + // Helpers... + // + + /** + * Process Definition with accompanying problems + */ + private static class CompiledProcessDefinition + { + public CompiledProcessDefinition(ProcessDefinition def, List problems) + { + this.def = def; + this.problems = new String[problems.size()]; + int i = 0; + for (Problem problem : problems) + { + this.problems[i++] = problem.toString(); + } + } + + protected ProcessDefinition def; + protected String[] problems; + } + + + /** + * Construct a Process Definition from the provided Process Definition + * stream + * + * @param workflowDefinition + * stream to create process definition from + * @param mimetype + * mimetype of stream + * @return process definition + */ + @SuppressWarnings("unchecked") + protected CompiledProcessDefinition compileProcessDefinition(InputStream definitionStream, String mimetype) + { + String actualMimetype = (mimetype == null) ? MimetypeMap.MIMETYPE_ZIP : mimetype; + CompiledProcessDefinition compiledDef = null; + + // parse process definition from jBPM process archive file + + if (actualMimetype.equals(MimetypeMap.MIMETYPE_ZIP)) + { + ZipInputStream zipInputStream = null; + try + { + zipInputStream = new ZipInputStream(definitionStream); + ProcessArchive reader = new ProcessArchive(zipInputStream); + ProcessDefinition def = reader.parseProcessDefinition(); + compiledDef = new CompiledProcessDefinition(def, reader.getProblems()); + } + catch(Exception e) + { + String msg = messageService.getMessage(ERR_COMPILE_PROCESS_DEF_zip); + throw new JbpmException(msg, e); + } + finally + { + if (zipInputStream != null) + { + try + { + zipInputStream.close(); + } + catch (IOException e) + { + // Intentionally empty! + } + } + } + } + + // parse process definition from jBPM xml file + + else if (actualMimetype.equals(MimetypeMap.MIMETYPE_XML)) + { + try + { + JBPMJpdlXmlReader jpdlReader = new JBPMJpdlXmlReader(definitionStream); + ProcessDefinition def = jpdlReader.readProcessDefinition(); + List problems = jpdlReader.getProblems(); + compiledDef = new CompiledProcessDefinition(def, problems); + } + catch(Exception e) + { + String msg = messageService.getMessage(ERR_COMPILE_PROCESS_DEF_XML); + throw new JbpmException(msg, e); + } + } + else + { + String msg = messageService.getMessage(ERR_COMPILE_PROCESS_DEF_UNSUPPORTED, mimetype); + throw new JbpmException(msg); + } + + if (tenantService.isEnabled()) + { + compiledDef.def.setName(tenantService.getName(compiledDef.def.getName())); + } + + return compiledDef; + } + + /** + * Get JBoss JBPM Id from Engine Global Id + * + * @param id + * global id + * @return JBoss JBPM Id + */ + protected long getJbpmId(String id) + { + try + { + String theLong = createLocalId(id); + return new Long(theLong); + } + catch(NumberFormatException e) + { + String msg = messageService.getMessage(ERR_GET_JBPM_ID, id); + throw new WorkflowException(msg, e); + } + } + + /** + * Get the JBoss JBPM Token for the Workflow Path + * + * @param session + * JBoss JBPM Graph Session + * @param pathId + * workflow path id + * @return JBoss JBPM Token + */ + protected Token getWorkflowToken(GraphSession session, String pathId) + { + // extract process id and token path within process + String[] path = pathId.split(WORKFLOW_PATH_SEPERATOR); + if (path.length != 2) + { + String msg = messageService.getMessage(ERR_GET_WORKFLOW_TOKEN_INVALID, pathId); + throw new WorkflowException(msg); + } + + // retrieve jBPM token for workflow position + ProcessInstance processInstance = getProcessInstance(session, path[0]); + String tokenId = path[1].replace(WORKFLOW_TOKEN_SEPERATOR, "/"); + Token token = processInstance.findToken(tokenId); + if (token == null) + { + String msg = messageService.getMessage(ERR_GET_WORKFLOW_TOKEN_NULL, pathId); + throw new WorkflowException(msg); + } + + return token; + } + + /** + * Gets Properties of Task + * + * @param instance task instance + * @param properties properties to set + */ + protected Map getTaskProperties(TaskInstance instance, boolean localProperties) + { + return getTaskProperties(instance, localProperties, null); + } + + /** + * Gets Properties of Task + * + * @param instance task instance + * @param properties properties to set + * @param variablesCache cahce of context instance variables if any exists + */ + @SuppressWarnings("unchecked") + protected Map getTaskProperties(TaskInstance instance, boolean localProperties, Map variablesCache) + { + // retrieve type definition for task + TypeDefinition taskDef = getFullTaskDefinition(instance); + Map taskProperties = taskDef.getProperties(); + Map taskAssocs = taskDef.getAssociations(); + + // build properties from jBPM context (visit all tokens to the root) + Map vars = instance.getVariablesLocally(); + if (!localProperties) + { + ContextInstance context = instance.getContextInstance(); + Token token = instance.getToken(); + while (token != null) + { + TokenVariableMap varMap = null; + if (variablesCache != null && variablesCache.containsKey(context.getId())) + { + varMap = variablesCache.get(context.getId()); + } + else + { + varMap = context.getTokenVariableMap(token); + } + if (varMap != null) + { + Map tokenVars = varMap.getVariablesLocally(); + for (Map.Entry entry : tokenVars.entrySet()) + { + if (!vars.containsKey(entry.getKey())) + { + vars.put(entry.getKey(), entry.getValue()); + } + } + } + token = token.getParent(); + } + } + + // map arbitrary task variables + Map properties = new HashMap(10); + for (Entry entry : vars.entrySet()) + { + String key = entry.getKey(); + QName qname = factory.mapNameToQName(key); + + // add variable, only if part of task definition or locally defined + // on task + boolean isAssoc = taskAssocs.containsKey(qname); + if (taskProperties.containsKey(qname) || isAssoc || instance.hasVariableLocally(key)) + { + Serializable value = convertValue(taskProperties.get(qname), entry.getValue()); + properties.put(qname, value); + } + } + + // map jBPM task instance fields to properties + properties.put(WorkflowModel.PROP_TASK_ID, instance.getId()); + properties.put(WorkflowModel.PROP_DESCRIPTION, instance.getDescription()); + properties.put(WorkflowModel.PROP_START_DATE, instance.getStart()); + properties.put(WorkflowModel.PROP_DUE_DATE, instance.getDueDate()); + properties.put(WorkflowModel.PROP_COMPLETION_DATE, instance.getEnd()); + properties.put(WorkflowModel.PROP_PRIORITY, instance.getPriority()); + properties.put(ContentModel.PROP_CREATED, instance.getCreate()); + properties.put(ContentModel.PROP_OWNER, instance.getActorId()); + + // map jBPM comments + // NOTE: Only use first comment in list + List comments = instance.getComments(); + if (comments != null && comments.size() > 0) + { + properties.put(WorkflowModel.PROP_COMMENT, comments.get(0).getMessage()); + } + + // map jBPM task instance collections to associations + Set pooledActors = instance.getPooledActors(); + if (pooledActors != null) + { + List pooledNodeRefs = new ArrayList(pooledActors.size()); + for (PooledActor pooledActor : pooledActors) + { + NodeRef pooledNodeRef = null; + String pooledActorId = pooledActor.getActorId(); + if (AuthorityType.getAuthorityType(pooledActorId) == AuthorityType.GROUP) + { + pooledNodeRef = mapNameToAuthority(pooledActorId); + } + else + { + pooledNodeRef = mapNameToPerson(pooledActorId); + } + if (pooledNodeRef != null) + { + pooledNodeRefs.add(pooledNodeRef); + } + } + properties.put(WorkflowModel.ASSOC_POOLED_ACTORS, (Serializable)pooledNodeRefs); + } + + return properties; + } + + private TypeDefinition getFullTaskDefinition(TaskInstance instance) + { + Task task = instance.getTask(); + TypeDefinition taskType = factory.getTaskTypeDefinition(task.getName(), task.getStartState() != null); + TypeDefinition taskDef = dictionaryService.getAnonymousType(taskType.getName()); + return taskDef; + } + + /** + * Sets Properties of Task + * + * @param instance + * task instance + * @param properties + * properties to set + */ + protected void setTaskProperties(TaskInstance instance, Map properties) + { + if (properties == null) + { + return; + } + + TypeDefinition taskDef = getFullTaskDefinition(instance); + Map taskProperties = taskDef.getProperties(); + Map taskAssocs = taskDef.getAssociations(); + + // map each parameter to task + for (Entry entry : properties.entrySet()) + { + QName key = entry.getKey(); + Serializable value = entry.getValue(); + + // determine if writing property + // NOTE: some properties map to fields on jBPM task instance whilst + // others are set in the general variable bag on the task + PropertyDefinition propDef = taskProperties.get(key); + if (propDef != null) + { + if (propDef.isProtected()) + { + // NOTE: only write non-protected properties + continue; + } + + // convert property value + if (value instanceof Collection) + { + value = (Serializable) DefaultTypeConverter.INSTANCE.convert(propDef.getDataType(), + (Collection) value); + } + else + { + value = (Serializable)DefaultTypeConverter.INSTANCE.convert(propDef.getDataType(), value); + } + + // convert NodeRefs to JBPMNodes + DataTypeDefinition dataTypeDef = propDef.getDataType(); + if (dataTypeDef.getName().equals(DataTypeDefinition.NODE_REF)) + { + value = convertNodeRefs(propDef.isMultiValued(), value); + } + + // map property to specific jBPM task instance field + if (key.equals(WorkflowModel.PROP_DESCRIPTION)) + { + if (value != null && !(value instanceof String)) + { + throw getInvalidPropertyValueException(key, value); + } + instance.setDescription((String)value); + continue; + } + if (key.equals(WorkflowModel.PROP_DUE_DATE)) + { + if (value != null && !(value instanceof Date)) + { + throw getInvalidPropertyValueException(key, value); + } + instance.setDueDate((Date)value); + continue; + } + else if (key.equals(WorkflowModel.PROP_PRIORITY)) + { + if (!(value instanceof Integer)) + { + throw getInvalidPropertyValueException(key, value); + } + instance.setPriority((Integer)value); + continue; + } + else if (key.equals(WorkflowModel.PROP_COMMENT)) + { + if (!(value instanceof String)) + { + throw getInvalidPropertyValueException(key, value); + } + + // NOTE: Only use first comment in list + final List comments = instance.getComments(); + if (comments != null && comments.size() > 0) + { + // remove existing comments + // TODO: jBPM does not provide assistance here + jbpmTemplate.execute(new JbpmCallback() + { + public Object doInJbpm(JbpmContext context) + { + Session session = context.getSession(); + for (Object obj: comments) + { + Comment comment = (Comment) obj; + comment.getToken().getComments().remove(comment); + session.delete(comment); + } + comments.clear(); + return null; + } + }); + } + instance.addComment((String)value); + continue; + } + else if (key.equals(ContentModel.PROP_OWNER)) + { + if (value != null && !(value instanceof String)) + { + throw getInvalidPropertyValueException(key, value); + } + String actorId = (String)value; + String existingActorId = instance.getActorId(); + if (existingActorId == null || !existingActorId.equals(actorId)) + { + instance.setActorId((String)value, false); + } + continue; + } + } + else + { + // determine if writing association + AssociationDefinition assocDef = taskAssocs.get(key); + if (assocDef != null) + { + // convert association to JBPMNodes + value = convertNodeRefs(assocDef.isTargetMany(), value); + + // map association to specific jBPM task instance field + if (key.equals(WorkflowModel.ASSOC_POOLED_ACTORS)) + { + String[] pooledActors = null; + if (value instanceof JBPMNodeList) + { + JBPMNodeList actors = (JBPMNodeList)value; + pooledActors = new String[actors.size()]; + int i = 0; + for (JBPMNode actor : actors) + { + pooledActors[i++] = mapAuthorityToName(actor.getNodeRef()); + } + } + else if (value instanceof JBPMNode) + { + JBPMNode node = (JBPMNode)value; + pooledActors = new String[] {mapAuthorityToName(node.getNodeRef())}; + } + else + { + throw getInvalidPropertyValueException(key, value); + } + instance.setPooledActors(pooledActors); + continue; + } + } + + // untyped value, perform minimal conversion + else + { + if (value instanceof NodeRef) + { + value = new JBPMNode((NodeRef)value, serviceRegistry); + } + } + } + + // no specific mapping to jBPM task has been established, so place + // into + // the generic task variable bag + String name = factory.mapQNameToName(key); + instance.setVariableLocally(name, value); + } + } + + private WorkflowException getInvalidPropertyValueException(QName key, Serializable value) + { + String msg = messageService.getMessage(ERR_SET_TASK_PROPS_INVALID_VALUE, value, key); + return new WorkflowException(msg); + } + + /** + * Sets Default Properties of Task + * + * @param instance + * task instance + */ + protected void setDefaultTaskProperties(TaskInstance instance) + { + Map existingValues = getTaskProperties(instance, true); + Map defaultValues = new HashMap(); + + // construct an anonymous type that flattens all mandatory aspects + ClassDefinition classDef = getFullTaskDefinition(instance); + Map propertyDefs = classDef.getProperties(); + + // for each property, determine if it has a default value + for (Map.Entry entry : propertyDefs.entrySet()) + { + String defaultValue = entry.getValue().getDefaultValue(); + if (defaultValue != null) + { + if (existingValues.get(entry.getKey()) == null) + { + defaultValues.put(entry.getKey(), defaultValue); + } + } + } + + // special case for task description default value + String description = (String)existingValues.get(WorkflowModel.PROP_DESCRIPTION); + if (description == null || description.length() == 0) + { + description = (String) instance.getContextInstance().getVariable( + factory.mapQNameToName(WorkflowModel.PROP_WORKFLOW_DESCRIPTION)); + if (description != null && description.length() > 0) + { + defaultValues.put(WorkflowModel.PROP_DESCRIPTION, description); + } + else + { + WorkflowTask task = createWorkflowTask(instance); + defaultValues.put(WorkflowModel.PROP_DESCRIPTION, task.getTitle()); + } + } + + // assign the default values to the task + if (defaultValues.size() > 0) + { + setTaskProperties(instance, defaultValues); + } + } + + /** + * Sets default description for the Task + * + * @param instance + * task instance + */ + public void setDefaultStartTaskDescription(TaskInstance instance) + { + String description = instance.getTask().getDescription(); + if (description == null || description.length() == 0) + { + description = (String) instance.getContextInstance().getVariable( + factory.mapQNameToName(WorkflowModel.PROP_WORKFLOW_DESCRIPTION)); + if (description != null && description.length() > 0) + { + Map defaultValues = new HashMap(); + defaultValues.put(WorkflowModel.PROP_DESCRIPTION, description); + setTaskProperties(instance, defaultValues); + } + } + } + + /** + * Initialise Workflow Instance properties + * + * @param startTask + * start task instance + */ + protected void setDefaultWorkflowProperties(TaskInstance startTask) + { + Map taskProperties = getTaskProperties(startTask, true); + + ContextInstance processContext = startTask.getContextInstance(); + String workflowDescriptionName = factory.mapQNameToName(WorkflowModel.PROP_WORKFLOW_DESCRIPTION); + if (!processContext.hasVariable(workflowDescriptionName)) + { + processContext.setVariable(workflowDescriptionName, taskProperties + .get(WorkflowModel.PROP_WORKFLOW_DESCRIPTION)); + } + String workflowDueDateName = factory.mapQNameToName(WorkflowModel.PROP_WORKFLOW_DUE_DATE); + if (!processContext.hasVariable(workflowDueDateName)) + { + processContext.setVariable(workflowDueDateName, taskProperties.get(WorkflowModel.PROP_WORKFLOW_DUE_DATE)); + } + String workflowPriorityName = factory.mapQNameToName(WorkflowModel.PROP_WORKFLOW_PRIORITY); + if (!processContext.hasVariable(workflowPriorityName)) + { + processContext.setVariable(workflowPriorityName, taskProperties.get(WorkflowModel.PROP_WORKFLOW_PRIORITY)); + } + String workflowPackageName = factory.mapQNameToName(WorkflowModel.ASSOC_PACKAGE); + if (!processContext.hasVariable(workflowPackageName)) + { + Serializable packageNodeRef = taskProperties.get(WorkflowModel.ASSOC_PACKAGE); + processContext.setVariable(workflowPackageName, convertNodeRefs(packageNodeRef instanceof List, + packageNodeRef)); + } + String workflowContextName = factory.mapQNameToName(WorkflowModel.PROP_CONTEXT); + if (!processContext.hasVariable(workflowContextName)) + { + Serializable contextRef = taskProperties.get(WorkflowModel.PROP_CONTEXT); + processContext.setVariable(workflowContextName, convertNodeRefs(contextRef instanceof List, contextRef)); + } + } + + /** + * Get missing mandatory properties on Task + * + * @param instance + * task instance + * @return array of missing property names (or null, if none) + */ + protected QName[] getMissingMandatoryTaskProperties(TaskInstance instance) + { + List missingProps = null; + + // retrieve properties of task + Map existingValues = getTaskProperties(instance, false); + + // retrieve definition of task + ClassDefinition classDef = getFullTaskDefinition(instance); + Map propertyDefs = classDef.getProperties(); + Map assocDefs = classDef.getAssociations(); + + // for each property, determine if it is mandatory + for (Map.Entry entry : propertyDefs.entrySet()) + { + QName name = entry.getKey(); + if (!(name.getNamespaceURI().equals(NamespaceService.CONTENT_MODEL_1_0_URI) || (name.getNamespaceURI() + .equals(NamespaceService.SYSTEM_MODEL_1_0_URI)))) + { + boolean isMandatory = entry.getValue().isMandatory(); + if (isMandatory) + { + Object value = existingValues.get(entry.getKey()); + if (value == null || (value instanceof String && ((String)value).length() == 0)) + { + if (missingProps == null) + { + missingProps = new ArrayList(); + } + missingProps.add(entry.getKey()); + } + } + } + } + for (Map.Entry entry : assocDefs.entrySet()) + { + QName name = entry.getKey(); + if (!(name.getNamespaceURI().equals(NamespaceService.CONTENT_MODEL_1_0_URI) || (name.getNamespaceURI() + .equals(NamespaceService.SYSTEM_MODEL_1_0_URI)))) + { + boolean isMandatory = entry.getValue().isTargetMandatory(); + if (isMandatory) + { + Object value = existingValues.get(entry.getKey()); + if (value == null || (value instanceof List && ((List)value).isEmpty())) + { + if (missingProps == null) + { + missingProps = new ArrayList(); + } + missingProps.add(entry.getKey()); + } + } + } + } + + return (missingProps == null) ? null : missingProps.toArray(new QName[missingProps.size()]); + } + + /** + * Attempts to convert a JBPM Object to the correct Alfresco data type + * @param propDef PropertyDefinition + * @param value any Value + * @return + */ + private Serializable convertValue(PropertyDefinition propDef, Object value) + { + if (propDef != null && value instanceof String && Boolean.class.getName().equals(propDef.getDataType().getJavaClassName())) + { + return (Serializable) new BooleanToStringConverter().revert(value); + } + else + { + return convertValue(value); + } + } + + /** + * Convert a jBPM Value to an Alfresco value + * + * @param value + * jBPM value + * @return alfresco value + */ + private Serializable convertValue(Object value) + { + Serializable alfValue = null; + + if (value == null) + { + // NOOP + } + else if (value instanceof JBPMNode) + { + alfValue = ((JBPMNode)value).getNodeRef(); + } + else if (value instanceof JBPMNodeList) + { + JBPMNodeList nodes = (JBPMNodeList)value; + List nodeRefs = new ArrayList(nodes.size()); + for (JBPMNode node : nodes) + { + nodeRefs.add(node.getNodeRef()); + } + alfValue = (Serializable)nodeRefs; + } + else if (value instanceof Serializable) + { + alfValue = (Serializable)value; + } + else + { + String msg = messageService.getMessage(ERR_CONVERT_VALUE, value); + throw new WorkflowException(msg); + } + return alfValue; + } + + /** + * Convert a Repository association to JBPMNodeList or JBPMNode + * + * @param isMany + * true => force conversion to list + * @param value + * value to convert + * @return JBPMNodeList or JBPMNode + */ + @SuppressWarnings("unchecked") + private Serializable convertNodeRefs(boolean isMany, Serializable value) + { + if (value instanceof NodeRef) + { + if (isMany) + { + // convert single node ref to list of node refs + JBPMNodeList values = new JBPMNodeList(); + values.add(new JBPMNode((NodeRef)value, serviceRegistry)); + value = values; + } + else + { + value = new JBPMNode((NodeRef)value, serviceRegistry); + } + } + else if (value instanceof List) + { + if (isMany) + { + JBPMNodeList values = new JBPMNodeList(); + for (NodeRef nodeRef : (List)value) + { + values.add(new JBPMNode(nodeRef, serviceRegistry)); + } + value = values; + } + else + { + List nodeRefs = (List)value; + value = (nodeRefs.size() == 0 ? null : new JBPMNode(nodeRefs.get(0), serviceRegistry)); + } + } + + return value; + } + + /** + * Convert person name to an Alfresco Person + * + * @param names + * the person name to convert + * @return the Alfresco person + */ + private NodeRef mapNameToPerson(String name) + { + NodeRef authority = null; + if (name != null) + { + // TODO: Should this be an exception? + if (personService.personExists(name)) + { + authority = personService.getPerson(name); + } + } + return authority; + } + + /** + * Convert authority name to an Alfresco Authority + * + * @param names + * the authority names to convert + * @return the Alfresco authorities + */ + private NodeRef mapNameToAuthority(String name) + { + NodeRef authority = null; + if (name != null) + { + // TODO: Should this be an exception? + if (authorityDAO.authorityExists(name)) + { + authority = authorityDAO.getAuthorityNodeRefOrNull(name); + } + } + return authority; + } + + /** + * Convert Alfresco authority to actor id + * + * @param authority + * @return actor id + */ + private String mapAuthorityToName(NodeRef authority) + { + String name = null; + QName type = nodeService.getType(authority); + + if (dictionaryService.isSubClass(type, ContentModel.TYPE_PERSON)) + { + name = (String)nodeService.getProperty(authority, ContentModel.PROP_USERNAME); + } + else + { + name = authorityDAO.getAuthorityName(authority); + } + return name; + } + + /** + * Get an I18N Label for a workflow item + * + * @param displayId + * message resource id lookup + * @param labelKey + * label to lookup (title or description) + * @param defaultLabel + * default value if not found in message resource bundle + * @return the label + */ + private String getLabel(String displayId, String labelKey, String defaultLabel) + { + String key = StringUtils.replace(displayId, ":", "_"); + key += "." + labelKey; + String label = messageService.getMessage(key); + + return (label == null) ? defaultLabel : label; + } + + /** + * Gets the Company Home + * + * @return company home node ref + */ + private NodeRef getCompanyHome() + { + if (tenantService.isEnabled()) + { + try + { + return tenantService.getRootNode(nodeService, serviceRegistry.getSearchService(), namespaceService, + companyHomePath, nodeService.getRootNode(companyHomeStore)); + } + catch (RuntimeException re) + { + String msg = messageService.getMessage(ERR_GET_COMPANY_HOME_INVALID, companyHomePath); + throw new IllegalStateException(msg, re); + } + } + else + { + List refs = unprotectedSearchService.selectNodes(nodeService.getRootNode(companyHomeStore), + companyHomePath, null, namespaceService, false); + if (refs.size() != 1) + { + String msg = messageService.getMessage(ERR_GET_COMPANY_HOME_MULTIPLE, companyHomePath, refs.size()); + throw new IllegalStateException(msg); + } + return refs.get(0); + } + } + + // + // Workflow Data Object Creation... + // + + /** + * Creates a Workflow Path + * + * @param token + * JBoss JBPM Token + * @param wfInstance + * @param node + * @return Workflow Path + */ + protected WorkflowPath createWorkflowPath(Token token) + { + if(token == null) + return null; + WorkflowInstance wfInstance = createWorkflowInstance(token.getProcessInstance()); + WorkflowNode node = createWorkflowNode(token.getNode()); + return createWorkflowPath(token, wfInstance, node); + } + + /** + * Creates a Workflow Path + * + * @param token + * JBoss JBPM Token + * @param wfInstance + * @param node + * @return Workflow Path + */ + protected WorkflowPath createWorkflowPath(Token token, WorkflowInstance wfInstance, WorkflowNode node) + { + String tokenId = token.getFullName().replace("/", WORKFLOW_TOKEN_SEPERATOR); + String id = token.getProcessInstance().getId() + WORKFLOW_PATH_SEPERATOR + tokenId; + boolean isActive = !token.hasEnded(); + return factory.createPath(id, wfInstance, node, isActive); + } + + /** + * Creates a Workflow Node + * + * @param node + * JBoss JBPM Node + * @return Workflow Node + */ + protected WorkflowNode createWorkflowNode(Node node) + { + if(node==null) + return null; + String processName = node.getProcessDefinition().getName(); + String name = node.getName(); + String type = getRealNode(node).getClass().getSimpleName(); + // TODO: Is there a formal way of determing if task node? + boolean isTaskNode = type.equals("TaskNode"); + List transitions = node.getLeavingTransitions(); + List wfTransitions; + if (transitions != null) + { + wfTransitions = new ArrayList(transitions.size()); + for (Transition transition : transitions) + { + wfTransitions.add(createWorkflowTransition(transition)); + } + } + else + { + wfTransitions = Collections.emptyList(); + } + WorkflowTransition[] transArr = wfTransitions.toArray(new WorkflowTransition[0]); + return factory.createNode(name, processName, name, null, type, isTaskNode, transArr); + } + + /** + * Create a Workflow Transition + * + * @param transition + * JBoss JBPM Transition + * @return Workflow Transition + */ + protected WorkflowTransition createWorkflowTransition(Transition transition) + { + if(transition==null) + return null; + String id = transition.getName(); + Node node = transition.getFrom(); + boolean isDefault = node.getDefaultLeavingTransition().equals(transition); + String title; + String description; + if (id == null || id.length() == 0) + { + title = getLabel(DEFAULT_TRANSITION_LABEL, TITLE_LABEL, id); + description = getLabel(DEFAULT_TRANSITION_LABEL, DESC_LABEL, title); + } + else + { + String nodeName = node.getName(); + String processName = node.getProcessDefinition().getName(); + title = getLabel(processName + ".node." + nodeName + ".transition." + id, TITLE_LABEL, id); + description = getLabel(processName + ".node." + nodeName + ".transition." + id, DESC_LABEL, title); + } + return new WorkflowTransition(id, title, description, isDefault); + } + + protected WorkflowInstance createWorkflowInstance(ProcessInstance instance) + { + return createWorkflowInstance(instance, null); + } + + @SuppressWarnings("unchecked") + private WorkflowInstance createWorkflowInstance(ProcessInstance instance, Date endDate) + { + if(instance == null) + return null; + + Map variables = instance.getContextInstance().getVariables(); + WorkflowDefinition definition = createWorkflowDefinition(instance.getProcessDefinition()); + return createWorkflowInstance(instance, definition, endDate, variables); + } + + /** + * Creates a Workflow Instance + * + * @param instance + * JBoss JBPM Process Instance + * @param endDate + * @param variables + * @return Workflow instance + */ + protected WorkflowInstance createWorkflowInstance(ProcessInstance instance, WorkflowDefinition definition, Date endDate, Map variables) + { + if(instance == null) + return null; + String id = Long.toString(instance.getId()); + Date startDate = instance.getStart(); + boolean isActive = false; + if (endDate == null) + { + isActive = !instance.hasEnded(); + endDate = instance.getEnd(); + } + return factory.createInstance(id, definition, variables, isActive, startDate, endDate); + } + + /** + * Creates a Workflow Definition + * + * @param definition + * JBoss Process Definition + * @return Workflow Definition + */ + protected WorkflowDefinition createWorkflowDefinition(ProcessDefinition definition) + { + if(definition==null) + return null; + Task startTask = definition.getTaskMgmtDefinition().getStartTask(); + return createWorkflowDefinition(definition, startTask); + } + + /** + * Creates a Workflow Definition + * + * @param definition + * JBoss Process Definition + * @return Workflow Definition + */ + private WorkflowDefinition createWorkflowDefinition(ProcessDefinition definition, Task startTask) + { + if(definition==null) + return null; + String id = Long.toString(definition.getId()); + String name = definition.getName(); + int version = definition.getVersion(); + WorkflowTaskDefinition startTaskDef = createWorkflowTaskDefinition(startTask); + return factory.createDefinition(id, name, version, name, null, startTaskDef); + } + + /** + * * Creates a Workflow Task + * @param task + * @return + */ + protected WorkflowTask createWorkflowTask(TaskInstance task) + { + WorkflowPath path = createWorkflowPath(task.getToken()); + Map properties = getTaskProperties(task, false); + WorkflowTaskDefinition definition = createWorkflowTaskDefinition(task.getTask()); + return createWorkflowTask(task, definition, path, properties); + } + + /** + * Creates a Workflow Task + * + * @param task + * JBoss Task Instance + * @param taskDef + * @param path + * @param properties + * @return Workflow Task + */ + private WorkflowTask createWorkflowTask(TaskInstance task, WorkflowTaskDefinition definition, WorkflowPath path, Map properties) + { + if(task == null) + return null; + String processName = task.getTask().getProcessDefinition().getName(); + if (tenantService.isEnabled()) + { + tenantService.checkDomain(processName); // throws exception if + // domain mismatch + } + String id = Long.toString(task.getId()); + String name = task.getName(); + WorkflowTaskState state = getWorkflowTaskState(task); + return factory.createTask(id, definition, name, null, null, state, path, properties); + } + + /** + * Creates a Workflow Task Definition + * + * @param task + * JBoss JBPM Task + * @return Workflow Task Definition + */ + protected WorkflowTaskDefinition createWorkflowTaskDefinition(Task task) + { + if (task == null) + return null; + String id = task.getName(); + boolean isStart = task.getStartState() != null; + Node node = isStart ? task.getStartState() : task.getTaskNode(); + WorkflowNode wfNode = createWorkflowNode(node); + return factory.createTaskDefinition(id, wfNode, id, isStart); + } + + /** + * Creates a Workflow Deployment + * + * @param compiledDef + * compiled JBPM process definition + * @return workflow deployment + */ + protected WorkflowDeployment createWorkflowDeployment(CompiledProcessDefinition compiledDef) + { + WorkflowDefinition definition = createWorkflowDefinition(compiledDef.def); + String[] problems = compiledDef.problems; + return factory.createDeployment(definition, problems); + } + + /** + * Creates a Workflow Timer + * + * @param timer + * jBPM Timer + * @return workflow timer + */ + protected WorkflowTimer createWorkflowTimer(Timer timer) + { + if(timer==null) + return null; + + WorkflowPath path = createWorkflowPath(timer.getToken()); + + WorkflowTask workflowTask = null; + TaskInstance taskInstance = timer.getTaskInstance(); + if (taskInstance != null) + { + workflowTask = createWorkflowTask(taskInstance); + } + + return factory.createWorkflowTimer(new Long(timer.getId()).toString(), timer.getName(), + timer.getException(), timer.getDueDate(), path, workflowTask); + } + + /** + * Get the Workflow Task State for the specified JBoss JBPM Task + * + * @param task + * task + * @return task state + */ + protected WorkflowTaskState getWorkflowTaskState(TaskInstance task) + { + if (task.hasEnded()) + { + return WorkflowTaskState.COMPLETED; + } + else + { + return WorkflowTaskState.IN_PROGRESS; + } + } + + /** + * Helper to retrieve the real jBPM Node + * + * @param node + * Node + * @return real Node (i.e. the one that's not a Hibernate proxy) + */ + private Node getRealNode(Node node) + { + if (node instanceof HibernateProxy) + { + Node realNode = (Node)((HibernateProxy)node).getHibernateLazyInitializer().getImplementation(); + return realNode; + } + else + { + return node; + } + } + + @Override + protected QName getDefaultStartTaskType() + { + return WorkflowModel.TYPE_START_TASK; + } + +} diff --git a/source/java/org/alfresco/service/cmr/action/ActionServiceTransientException.java b/source/java/org/alfresco/service/cmr/action/ActionServiceTransientException.java new file mode 100644 index 0000000000..d550fc4907 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/action/ActionServiceTransientException.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2005-2012 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.service.cmr.action; + +/** + * This exception should be thrown when an {@link Action} has not been run successfully due to + * a transient condition and where it is possible that a subsequent request to execute the + * same action might succeed. + *

+ * An example of this would be the case where a request to create a thumbnail + * has failed because the necessary thumbnailing software is not available e.g. because the OpenOffice.org process + * is not currently running. + *

+ * The {@link ActionService} can be configured to run a {@link Action#setCompensatingAction(Action) compensating action} + * when another action fails with an exception. If however the exception thrown is an instance of {@link ActionServiceTransientException} + * then this compensating action will not be run. + * + * @author Neil Mc Erlean + * @since 4.0.1 + */ +public class ActionServiceTransientException extends ActionServiceException +{ + private static final long serialVersionUID = 3257571685241467958L; + + public ActionServiceTransientException(String msgId) + { + super(msgId); + } + + public ActionServiceTransientException(String msgId, Object[] msgParams) + { + super(msgId, msgParams); + } + + public ActionServiceTransientException(String msgId, Object[] msgParams, Throwable cause) + { + super(msgId, msgParams, cause); + } + + public ActionServiceTransientException(String msgId, Throwable cause) + { + super(msgId, cause); + } +} diff --git a/source/java/org/alfresco/service/cmr/action/ActionStatus.java b/source/java/org/alfresco/service/cmr/action/ActionStatus.java index 38e1e2f7b8..20a47a275d 100644 --- a/source/java/org/alfresco/service/cmr/action/ActionStatus.java +++ b/source/java/org/alfresco/service/cmr/action/ActionStatus.java @@ -58,7 +58,14 @@ public enum ActionStatus * {@link Action#getExecutionFailureCause()} to find * out why. */ - Failed + Failed, + /** + * The Action failed with a transient exception. Call + * {@link Action#getExecutionFailureCause()} to find + * out why. + * @since 4.0.1 + */ + Declined ; public static ActionStatus valueOf(Serializable s) diff --git a/source/java/org/alfresco/service/cmr/email/EmailMessage.java b/source/java/org/alfresco/service/cmr/email/EmailMessage.java index f149a570d9..f058bb6526 100644 --- a/source/java/org/alfresco/service/cmr/email/EmailMessage.java +++ b/source/java/org/alfresco/service/cmr/email/EmailMessage.java @@ -51,7 +51,8 @@ public interface EmailMessage extends Serializable public Date getSentDate(); /** - * @return subject of the message. + * Get the subject of the message + * @return subject of the message or null if there is no subject. */ public String getSubject(); diff --git a/source/java/org/alfresco/service/cmr/security/PersonService.java b/source/java/org/alfresco/service/cmr/security/PersonService.java index 17a4485154..324b28dd57 100644 --- a/source/java/org/alfresco/service/cmr/security/PersonService.java +++ b/source/java/org/alfresco/service/cmr/security/PersonService.java @@ -326,4 +326,11 @@ public interface PersonService @NotAuditable public String getUserIdentifier(String caseSensitiveUserName); + /** + * Counts the number of persons registered with the system. + * + * @return + */ + @NotAuditable + public int countPeople(); } diff --git a/source/java/org/alfresco/service/cmr/workflow/WorkflowService.java b/source/java/org/alfresco/service/cmr/workflow/WorkflowService.java index 1a04a32de2..d04d03999c 100644 --- a/source/java/org/alfresco/service/cmr/workflow/WorkflowService.java +++ b/source/java/org/alfresco/service/cmr/workflow/WorkflowService.java @@ -297,6 +297,15 @@ public interface WorkflowService @Auditable(parameters = {"workflowId"}) public WorkflowInstance cancelWorkflow(String workflowId); + /** + * Cancel a batch of "in-flight" Workflow instances + * + * @param workflowIds List of the workflow instances to cancel + * @return List of updated representations of the workflow instances + */ + @Auditable(parameters = {"workflowIds"}) + public List cancelWorkflows(List workflowIds); + /** * Delete an "in-flight" Workflow instance * @@ -347,6 +356,18 @@ public interface WorkflowService @Auditable(parameters = {"pathId"}) public WorkflowTask getStartTask(String workflowInstanceId); + /** + * Gets the start task instances for the given workflow instances. + * + * @param workflowInstanceIds + * @param sameSession indicates that the returned {@link WorkflowTask} elements will be used in + * the same session. If {@code true}, the returned List will be a lazy loaded list + * providing greater performance. + * @return + */ + @Auditable(parameters = {"pathIds"}) + public List getStartTasks(List workflowInstanceIds, boolean sameSession); + /** * Determines if a graphical view of the workflow instance exists * @@ -412,14 +433,23 @@ public interface WorkflowService @Auditable(parameters = {"authority"}) public List getPooledTasks(String authority); + /** + * @deprecated Use overloaded method with the {@code sameSession} parameter + * (this method defaults the parameter to {@code false}). + */ + public List queryTasks(WorkflowTaskQuery query); + /** * Query for tasks * * @param query the filter by which tasks are queried + * @param sameSession indicates that the returned {@link WorkflowTask} elements will be used in + * the same session. If {@code true}, the returned List will be a lazy loaded list + * providing greater performance. * @return the list of tasks matching the specified query */ @Auditable(parameters = {"query"}) - public List queryTasks(WorkflowTaskQuery query); + public List queryTasks(WorkflowTaskQuery query, boolean sameSession); /** * Update the Properties and Associations of a Task diff --git a/source/java/org/alfresco/util/ScriptPagingDetails.java b/source/java/org/alfresco/util/ScriptPagingDetails.java index 051f6210fc..77e61edc05 100644 --- a/source/java/org/alfresco/util/ScriptPagingDetails.java +++ b/source/java/org/alfresco/util/ScriptPagingDetails.java @@ -191,7 +191,7 @@ public class ScriptPagingDetails extends PagingRequest this.totalItems = min; this.confidence = ItemsSizeConfidence.AT_LEAST; } - else if(min == max) + else if(min.equals(max)) { this.totalItems = min; this.confidence = ItemsSizeConfidence.EXACT; diff --git a/source/java/org/alfresco/util/schemacomp/ComparisonUtils.java b/source/java/org/alfresco/util/schemacomp/ComparisonUtils.java index 22201cbeaa..c02c4c543f 100644 --- a/source/java/org/alfresco/util/schemacomp/ComparisonUtils.java +++ b/source/java/org/alfresco/util/schemacomp/ComparisonUtils.java @@ -22,9 +22,9 @@ package org.alfresco.util.schemacomp; import java.util.Collection; import java.util.List; -import org.alfresco.util.schemacomp.Result.Strength; import org.alfresco.util.schemacomp.model.DbObject; -import org.alfresco.util.schemacomp.model.Schema; + +import com.google.gdata.data.extensions.Where; /** * Utilities for comparing data structures in the context of comparing two database schemas. @@ -35,46 +35,49 @@ public interface ComparisonUtils { List findEquivalentObjects(DbObject rootObject, DbObject objToMatch); - void compareSimpleCollections(DbProperty leftProperty, DbProperty rightProperty, - DiffContext ctx, Strength strength); + + /** + * Compare two {@link List}s of 'simple' (i.e. non-{@link DbObject}) objects. Ordering + * is significant - if an element E appears in both collections but at different indexes + * then it is not considered to be the same item. + * + * @param leftProperty + * @param rightProperty + * @param ctx + */ + void compareSimpleOrderedLists(DbProperty leftProperty, DbProperty rightProperty, DiffContext ctx); /** - * Compare collections, reporting differences using the default {@link Difference.Strength} + * Compare two collections. Similar to {@link #compareSimpleOrderedLists(DbProperty, DbProperty, DiffContext)} + * except that this method operates on {@link Collection}s and order (and cardinality) is not important. If + * an element E from the reference collection appears one or more times at any position in the target collection + * then that element is said to be {@link Where#IN_BOTH_NO_DIFFERENCE in both with no difference}. * - * @see #compareCollections(Collection, Collection, Differences, Strength) + * @param leftProperty + * @param rightProperty + * @param ctx */ - void compareCollections(Collection leftCollection, - Collection rightCollection, DiffContext ctx); - + void compareSimpleCollections(DbProperty leftProperty, DbProperty rightProperty, DiffContext ctx); + /** * Compare collections of {@link DbObject}s using their {@link DbObject#diff(DbObject, Differences)} method. - * Differences are reported using the specified {@link Difference.Strength}. * * @param leftCollection * @param rightCollection * @param differences - * @param strength */ void compareCollections(Collection leftCollection, - Collection rightCollection, DiffContext ctx, - Strength strength); + Collection rightCollection, DiffContext ctx); + /** - * Compare two simple objects. Differences are reported using the default Result.Strength. - * - * @see #compareSimple(Object, Object, Differences, Strength) - */ - void compareSimple(DbProperty left, DbProperty right, DiffContext ctx); - - /** - * Compare two 'simple' (i.e. non-{@link DbObject} objects) using their {@link Object#equals(Object)} method - * to decide if there is a difference. Differences are reported using the Result.Strength specified. + * Compare two 'simple' (i.e. non-{@link DbObject}) objects using their {@link Object#equals(Object)} method + * to decide if there is a difference. * * @param left * @param right * @param differences - * @param strength */ - void compareSimple(DbProperty left, DbProperty right, DiffContext ctx, Strength strength); + void compareSimple(DbProperty left, DbProperty right, DiffContext ctx); } \ No newline at end of file diff --git a/source/java/org/alfresco/util/schemacomp/DbObjectXMLTransformer.java b/source/java/org/alfresco/util/schemacomp/DbObjectXMLTransformer.java index 8108c00726..9a2b986940 100644 --- a/source/java/org/alfresco/util/schemacomp/DbObjectXMLTransformer.java +++ b/source/java/org/alfresco/util/schemacomp/DbObjectXMLTransformer.java @@ -67,18 +67,25 @@ public class DbObjectXMLTransformer // and a name attribute corresponding to the value of getName(), // e.g. For an instance of a Column: final AttributesImpl attribs = new AttributesImpl(); + if (dbObject instanceof Schema) + { + // XML Schema (XSD) declarations. + attribs.addAttribute("", "", "xmlns", "CDATA", "http://www.alfresco.org/repo/db-schema"); + attribs.addAttribute("", "", "xmlns:xsi", "CDATA", "http://www.w3.org/2001/XMLSchema-instance"); + attribs.addAttribute("", "", "xsi:schemaLocation", "CDATA", "http://www.alfresco.org/repo/db-schema db-schema.xsd"); + } attribs.addAttribute("", "", XML.ATTR_NAME, "CDATA", dbObject.getName()); - // Add class-specific attributes. + // Add class-specific attributes (after common DbObject attributes). addAttributes(dbObject, attribs); String tagName = dbObject.getClass().getSimpleName().toLowerCase(); xmlOut.startElement("", "", tagName, attribs); + // All DbObjects potentially have validator configuration present in the XML. + transformValidators(dbObject.getValidators()); // The element's contents can optionally be populated with class-specific content. transformDbObject(dbObject); - // All DbObjects potentially have validator configuration present in the XML. - transformValidators(dbObject.getValidators()); // Provide the end tag, or close an empty element. xmlOut.endElement("", "", tagName); @@ -130,7 +137,13 @@ public class DbObjectXMLTransformer */ private void addAttributes(DbObject dbObject, AttributesImpl attribs) { - if (dbObject instanceof Index) + if (dbObject instanceof Schema) + { + Schema schema = (Schema) dbObject; + attribs.addAttribute("", "", XML.ATTR_DB_PREFIX, "CDATA", schema.getDbPrefix()); + attribs.addAttribute("", "", XML.ATTR_VERSION, "CDATA", Integer.toString(schema.getVersion())); + } + else if (dbObject instanceof Index) { Index index = (Index) dbObject; attribs.addAttribute("", "", XML.ATTR_UNIQUE, "CDATA", Boolean.toString(index.isUnique())); diff --git a/source/java/org/alfresco/util/schemacomp/DbObjectXMLTransformerTest.java b/source/java/org/alfresco/util/schemacomp/DbObjectXMLTransformerTest.java index 793d19a8cb..6f67e7ea15 100644 --- a/source/java/org/alfresco/util/schemacomp/DbObjectXMLTransformerTest.java +++ b/source/java/org/alfresco/util/schemacomp/DbObjectXMLTransformerTest.java @@ -191,19 +191,24 @@ public class DbObjectXMLTransformerTest Table tableOne = new Table(null, "table_one", columns, pk, fks, indexes); Table tableTwo = new Table(null, "table_two", columns, pk, fks, indexes); - Schema schema = new Schema("my_schema"); + Schema schema = new Schema("my_schema", "alf_", 132); schema.add(tableOne); schema.add(tableTwo); schema.add(new Sequence(null, "sequence_one")); schema.add(new Sequence(null, "sequence_two")); schema.add(new Sequence(null, "sequence_three")); + schema.setValidators(new ArrayList()); transformer.output(schema); BufferedReader reader = new BufferedReader(new StringReader(writer.toString())); dumpOutput(); assertHasPreamble(reader); - assertEquals("", reader.readLine()); + assertEquals("", reader.readLine()); assertEquals(" ", reader.readLine()); skipUntilEnd(" {table}", reader); skipUntilEnd(" {table}", reader); @@ -282,6 +287,13 @@ public class DbObjectXMLTransformerTest dumpOutput(); assertHasPreamble(reader); assertEquals("", reader.readLine()); + assertEquals(" ", reader.readLine()); + assertEquals(" ", reader.readLine()); + assertEquals(" ", reader.readLine()); + assertEquals(" match_me_if_you_can", reader.readLine()); + assertEquals(" ", reader.readLine()); + assertEquals(" ", reader.readLine()); + assertEquals(" ", reader.readLine()); assertEquals(" ", reader.readLine()); skipUntilEnd(" {column}", reader); skipUntilEnd(" {column}", reader); @@ -295,13 +307,6 @@ public class DbObjectXMLTransformerTest skipUntilEnd(" {index}", reader); skipUntilEnd(" {index}", reader); assertEquals(" ", reader.readLine()); - assertEquals(" ", reader.readLine()); - assertEquals(" ", reader.readLine()); - assertEquals(" ", reader.readLine()); - assertEquals(" match_me_if_you_can", reader.readLine()); - assertEquals(" ", reader.readLine()); - assertEquals(" ", reader.readLine()); - assertEquals(" ", reader.readLine()); assertEquals("
", reader.readLine()); } diff --git a/source/java/org/alfresco/util/schemacomp/DbToXML.java b/source/java/org/alfresco/util/schemacomp/DbToXML.java index ab6b76be9c..56d9645cf7 100644 --- a/source/java/org/alfresco/util/schemacomp/DbToXML.java +++ b/source/java/org/alfresco/util/schemacomp/DbToXML.java @@ -65,31 +65,17 @@ public class DbToXML public void execute() { - ExportDb exporter = null; - try - { - exporter = new ExportDb(context); - exporter.setNamePrefix(namePrefix); - exporter.execute(); - } - catch (Exception e) - { - System.err.println("Unable to read database schema."); - e.printStackTrace(); - System.exit(1); - } - - if (exporter != null) - { - Schema schema = exporter.getSchema(); - // Write to a string buffer and then write the results to a file - // since we need to write windows line endings - and even passing in a suitable - // PrintWriter to StreamResult does not seem to result in the correct line endings. - StringWriter stringWriter = new StringWriter(); - SchemaToXML schemaToXML = new SchemaToXML(schema, new StreamResult(stringWriter)); - schemaToXML.execute(); - writeToFile(stringWriter.getBuffer().toString()); - } + ExportDb exporter = new ExportDb(context); + exporter.setNamePrefix(namePrefix); + exporter.execute(); + Schema schema = exporter.getSchema(); + // Write to a string buffer and then write the results to a file + // since we need to write windows line endings - and even passing in a suitable + // PrintWriter to StreamResult does not seem to result in the correct line endings. + StringWriter stringWriter = new StringWriter(); + SchemaToXML schemaToXML = new SchemaToXML(schema, new StreamResult(stringWriter)); + schemaToXML.execute(); + writeToFile(stringWriter.getBuffer().toString()); } private void writeToFile(String content) diff --git a/source/java/org/alfresco/util/schemacomp/DefaultComparisonUtils.java b/source/java/org/alfresco/util/schemacomp/DefaultComparisonUtils.java index 4a90421440..d2694b42cb 100644 --- a/source/java/org/alfresco/util/schemacomp/DefaultComparisonUtils.java +++ b/source/java/org/alfresco/util/schemacomp/DefaultComparisonUtils.java @@ -24,11 +24,8 @@ import java.util.Collection; import java.util.List; import org.alfresco.util.schemacomp.Difference.Where; -import org.alfresco.util.schemacomp.Result.Strength; import org.alfresco.util.schemacomp.model.DbObject; -import org.alfresco.util.schemacomp.model.Schema; -import org.apache.commons.collections.CollectionUtils; -import org.apache.commons.collections.Predicate; +import org.alfresco.util.schemacomp.validator.DbValidator; /** * A collection of utility methods for determining differences between two database schemas. @@ -49,15 +46,100 @@ public class DefaultComparisonUtils implements ComparisonUtils @Override - public void compareSimpleCollections(DbProperty leftProp, - DbProperty rightProp, DiffContext ctx, Strength strength) + public void compareSimpleOrderedLists(DbProperty refProp, DbProperty targetProp, DiffContext ctx) { + checkPropertyContainsList(refProp); + checkPropertyContainsList(targetProp); + + // Check whether the leftProperty should be compared to the rightProperty + DbObject leftDbObject = refProp.getDbObject(); + if (leftDbObject.hasValidators()) + { + for (DbValidator validator : leftDbObject.getValidators()) + { + if (validator.validates(refProp.getPropertyName())) + { + // Don't perform differencing on this property - a validator will handle it. + return; + } + } + } + + @SuppressWarnings("unchecked") + ArrayList refList = new ArrayList((List) refProp.getPropertyValue()); + @SuppressWarnings("unchecked") + ArrayList targetList = new ArrayList((List) targetProp.getPropertyValue()); + + Results differences = ctx.getComparisonResults(); + + int maxSize = Math.max(refList.size(), targetList.size()); + + for (int i = 0; i < maxSize; i++) + { + if (i < refList.size() && i < targetList.size()) + { + DbProperty refIndexedProp = new DbProperty(refProp.getDbObject(), refProp.getPropertyName(), i); + DbProperty targetIndexedProp = new DbProperty(targetProp.getDbObject(), targetProp.getPropertyName(), i); + + if (refList.get(i).equals(targetList.get(i))) + { + differences.add(Where.IN_BOTH_NO_DIFFERENCE, refIndexedProp, targetIndexedProp); + } + else + { + differences.add(Where.IN_BOTH_BUT_DIFFERENCE, refIndexedProp, targetIndexedProp); + } + } + else if (i < refList.size()) + { + DbProperty indexedProp = new DbProperty(refProp.getDbObject(), refProp.getPropertyName(), i); + differences.add(Where.ONLY_IN_REFERENCE, indexedProp, null); + } + else + { + DbProperty indexedProp = new DbProperty(targetProp.getDbObject(), targetProp.getPropertyName(), i); + // No equivalent object in the reference collection. + differences.add(Where.ONLY_IN_TARGET, null, indexedProp); + } + } + } + + /** + * Ensure the property is carrying a list as its payload. A List is required + * rather than a Collection as the latter may not be ordered. + * + * @param prop + */ + private void checkPropertyContainsList(DbProperty prop) + { + if (!List.class.isAssignableFrom(prop.getPropertyValue().getClass())) + { + throw new IllegalArgumentException("List required, but was " + prop.getPropertyValue().getClass()); + } + } + + @Override + public void compareSimpleCollections(DbProperty leftProp, + DbProperty rightProp, DiffContext ctx) + { + // Check whether the leftProperty should be compared to the rightProperty + DbObject leftDbObject = leftProp.getDbObject(); + if (leftDbObject.hasValidators()) + { + for (DbValidator validator : leftDbObject.getValidators()) + { + if (validator.validates(leftProp.getPropertyName())) + { + // Don't perform differencing on this property - a validator will handle it. + return; + } + } + } @SuppressWarnings("unchecked") Collection leftCollection = (Collection) leftProp.getPropertyValue(); @SuppressWarnings("unchecked") Collection rightCollection = (Collection) rightProp.getPropertyValue(); - // TODO: Temporary code during refactoring ArrayList leftList = new ArrayList(leftCollection); ArrayList rightList = new ArrayList(rightCollection); @@ -76,13 +158,13 @@ public class DefaultComparisonUtils implements ComparisonUtils // with a 'simple' value — as there is no way of knowing if the term represents the same value // (e.g. two strings {red_value, green_value}, are these meant to be the same or different?) DbProperty rightIndexedProp = new DbProperty(rightProp.getDbObject(), rightProp.getPropertyName(), rightIndex); - differences.add(Where.IN_BOTH_NO_DIFFERENCE, leftIndexedProp, rightIndexedProp, strength); + differences.add(Where.IN_BOTH_NO_DIFFERENCE, leftIndexedProp, rightIndexedProp); } else { // No equivalent object in the right hand collection. // Using rightIndexedProperty would result in index out of bounds error. - differences.add(Where.ONLY_IN_REFERENCE, leftIndexedProp, rightProp, strength); + differences.add(Where.ONLY_IN_REFERENCE, leftIndexedProp, rightProp); } } @@ -94,39 +176,26 @@ public class DefaultComparisonUtils implements ComparisonUtils { DbProperty rightIndexedProp = new DbProperty(rightProp.getDbObject(), rightProp.getPropertyName(), rightIndex); // No equivalent object in the left hand collection. - differences.add(Where.ONLY_IN_TARGET, leftProp, rightIndexedProp, strength); + differences.add(Where.ONLY_IN_TARGET, leftProp, rightIndexedProp); } } } - /** - * Compare collections, reporting differences using the default {@link Difference.Strength} - * - * @see #compareCollections(Collection, Collection, Results, Strength) - */ + @Override public void compareCollections(Collection leftCollection, Collection rightCollection, DiffContext ctx) - { - compareCollections(leftCollection, rightCollection, ctx, null); - } - - /** - * Compare collections of {@link DbObject}s using their {@link DbObject#diff(DbObject, Results)} method. - * Differences are reported using the specified {@link Difference.Strength}. - * - * @param leftCollection - * @param rightCollection - * @param differences - * @param strength - */ - @Override - public void compareCollections(Collection leftCollection, - Collection rightCollection, DiffContext ctx, Strength strength) { Results differences = ctx.getComparisonResults(); for (DbObject leftObj : leftCollection) - { + { + if (leftObj.hasObjectLevelValidator()) + { + // Don't report differences regarding this object - there is a validator + // that takes sole responsibility for doing so. + continue; + } + boolean foundMatch = false; for (DbObject rootObject : rightCollection) @@ -136,7 +205,7 @@ public class DefaultComparisonUtils implements ComparisonUtils for (DbObject match : matches) { // There is an equivalent object in the right hand collection as in the left. - leftObj.diff(match, ctx, strength); + leftObj.diff(match, ctx); } if (matches.size() > 0) @@ -148,13 +217,20 @@ public class DefaultComparisonUtils implements ComparisonUtils if (!foundMatch) { // No equivalent object in the target collection. - differences.add(Where.ONLY_IN_REFERENCE, new DbProperty(leftObj, null), null, strength); + differences.add(Where.ONLY_IN_REFERENCE, new DbProperty(leftObj, null), null); } } // Identify objects in the right collection but not the left for (DbObject rightObj : rightCollection) { + if (rightObj.hasObjectLevelValidator()) + { + // Don't report differences regarding this object - there is a validator + // that takes sole responsibility for doing so. + continue; + } + boolean foundMatch = false; for (DbObject rootObject : leftCollection) @@ -170,34 +246,29 @@ public class DefaultComparisonUtils implements ComparisonUtils if (!foundMatch) { // No equivalent object in the left hand collection. - differences.add(Where.ONLY_IN_TARGET, null, new DbProperty(rightObj, null), strength); + differences.add(Where.ONLY_IN_TARGET, null, new DbProperty(rightObj, null)); } } } - /** - * Compare two simple objects. Differences are reported using the default Result.Strength. - * - * @see #compareSimple(Object, Object, Results, Strength) - */ - @Override - public void compareSimple(DbProperty left, DbProperty right, DiffContext ctx) - { - compareSimple(left, right, ctx, null); - } - /** - * Compare two 'simple' (i.e. non-{@link DbObject} objects) using their {@link Object#equals(Object)} method - * to decide if there is a difference. Differences are reported using the Result.Strength specified. - * - * @param left - * @param right - * @param differences - * @param strength - */ @Override - public void compareSimple(DbProperty leftProperty, DbProperty rightProperty, DiffContext ctx, Strength strength) + public void compareSimple(DbProperty leftProperty, DbProperty rightProperty, DiffContext ctx) { + // Check whether the leftProperty should be compared to the rightProperty + DbObject leftDbObject = leftProperty.getDbObject(); + if (leftDbObject.hasValidators()) + { + for (DbValidator validator : leftDbObject.getValidators()) + { + if (validator.validates(leftProperty.getPropertyName())) + { + // Don't perform differencing on this property - a validator will handle it. + return; + } + } + } + Where where = null; @@ -234,7 +305,7 @@ public class DefaultComparisonUtils implements ComparisonUtils } } - ctx.getComparisonResults().add(where, leftProperty, rightProperty, strength); + ctx.getComparisonResults().add(where, leftProperty, rightProperty); } diff --git a/source/java/org/alfresco/util/schemacomp/DefaultComparisonUtilsTest.java b/source/java/org/alfresco/util/schemacomp/DefaultComparisonUtilsTest.java index ae2e18b55f..be2cd142a7 100644 --- a/source/java/org/alfresco/util/schemacomp/DefaultComparisonUtilsTest.java +++ b/source/java/org/alfresco/util/schemacomp/DefaultComparisonUtilsTest.java @@ -19,9 +19,8 @@ package org.alfresco.util.schemacomp; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -31,15 +30,13 @@ import java.util.Collections; import java.util.List; import org.alfresco.util.schemacomp.Difference.Where; -import org.alfresco.util.schemacomp.Result.Strength; import org.alfresco.util.schemacomp.model.AbstractDbObject; import org.alfresco.util.schemacomp.model.DbObject; +import org.alfresco.util.schemacomp.validator.DbValidator; import org.hibernate.dialect.Dialect; -import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; @@ -66,20 +63,20 @@ public class DefaultComparisonUtilsTest @Test public void compareSimple() { - comparisonUtils.compareSimple(prop(null), prop(null), ctx, Strength.ERROR); - verify(differences).add(Where.IN_BOTH_NO_DIFFERENCE, prop(null), prop(null), Strength.ERROR); + comparisonUtils.compareSimple(prop(null), prop(null), ctx); + verify(differences).add(Where.IN_BOTH_NO_DIFFERENCE, prop(null), prop(null)); - comparisonUtils.compareSimple(prop("not_null_string"), prop("not_null_string"), ctx, Strength.ERROR); - verify(differences).add(Where.IN_BOTH_NO_DIFFERENCE, prop("not_null_string"), prop("not_null_string"), Strength.ERROR); + comparisonUtils.compareSimple(prop("not_null_string"), prop("not_null_string"), ctx); + verify(differences).add(Where.IN_BOTH_NO_DIFFERENCE, prop("not_null_string"), prop("not_null_string")); - comparisonUtils.compareSimple(prop("left"), prop("right"), ctx, Strength.ERROR); - verify(differences).add(Where.IN_BOTH_BUT_DIFFERENCE, prop("left"), prop("right"), Strength.ERROR); + comparisonUtils.compareSimple(prop("left"), prop("right"), ctx); + verify(differences).add(Where.IN_BOTH_BUT_DIFFERENCE, prop("left"), prop("right")); - comparisonUtils.compareSimple(prop("left"), prop(null), ctx, Strength.ERROR); - verify(differences).add(Where.ONLY_IN_REFERENCE, prop("left"), prop(null), Strength.ERROR); + comparisonUtils.compareSimple(prop("left"), prop(null), ctx); + verify(differences).add(Where.ONLY_IN_REFERENCE, prop("left"), prop(null)); - comparisonUtils.compareSimple(prop(null), prop("right"), ctx, Strength.ERROR); - verify(differences).add(Where.ONLY_IN_TARGET, prop(null), prop("right"), Strength.ERROR); + comparisonUtils.compareSimple(prop(null), prop("right"), ctx); + verify(differences).add(Where.ONLY_IN_TARGET, prop(null), prop("right")); } public DbProperty prop(String propValue) @@ -103,12 +100,12 @@ public class DefaultComparisonUtilsTest Collection right = new ArrayList(); Collections.addAll(right, db1, db3, db4); - comparisonUtils.compareCollections(left, right, ctx, Strength.ERROR); + comparisonUtils.compareCollections(left, right, ctx); // Differences and ommissions are noticed... verify(differences).add(Where.IN_BOTH_BUT_DIFFERENCE, new DbProperty(db1), new DbProperty(db1)); - verify(differences).add(Where.ONLY_IN_REFERENCE, new DbProperty(db2), null, Strength.ERROR); - verify(differences).add(Where.ONLY_IN_TARGET, null, new DbProperty(db3), Strength.ERROR); + verify(differences).add(Where.ONLY_IN_REFERENCE, new DbProperty(db2), null); + verify(differences).add(Where.ONLY_IN_TARGET, null, new DbProperty(db3)); verify(differences).add(Where.IN_BOTH_BUT_DIFFERENCE, new DbProperty(db4), new DbProperty(db4)); } @@ -126,15 +123,231 @@ public class DefaultComparisonUtilsTest Collection right = new ArrayList(); Collections.addAll(right, db1, db2, db3); - comparisonUtils.compareCollections(left, right, ctx, Strength.ERROR); + comparisonUtils.compareCollections(left, right, ctx); // Differences and ommissions are noticed... - verify(differences).add(Where.ONLY_IN_REFERENCE, new DbProperty(db4), null, Strength.ERROR); + verify(differences).add(Where.ONLY_IN_REFERENCE, new DbProperty(db4), null); verify(differences).add(Where.IN_BOTH_BUT_DIFFERENCE, new DbProperty(db1), new DbProperty(db1)); verify(differences).add(Where.IN_BOTH_BUT_DIFFERENCE, new DbProperty(db1), new DbProperty(db2)); verify(differences).add(Where.IN_BOTH_BUT_DIFFERENCE, new DbProperty(db1), new DbProperty(db3)); } + @Test + public void propertyIsNotComparedWhenValidatorTakesResponsibility() + { + DbObject db1 = new DatabaseObject("db1"); + DbProperty db1NameProp = new DbProperty(db1, "name"); + DbObject db2 = new DatabaseObject("db2"); + DbProperty db2NameProp = new DbProperty(db2, "name"); + + // Using mock to decouple unit test from actual NameValidator. + DbValidator nameValidator = mock(DbValidator.class); + when(nameValidator.validates("name")).thenReturn(true); + db1.getValidators().add(nameValidator); + + comparisonUtils.compareSimple(db1NameProp, db2NameProp, ctx); + + verify(differences, never()).add(Where.IN_BOTH_BUT_DIFFERENCE, db1NameProp, db2NameProp); + } + + @Test + public void collectionPropertyIsNotComparedWhenValidatorTakesResponsibility() + { + Collection refCollection = new ArrayList(); + refCollection.add(123); + refCollection.add("both"); + + DbObject refDbObj = new DbObjectWithCollection("left", refCollection); + DbProperty refCollProp = new DbProperty(refDbObj, "collection"); + + Collection targetCollection = new ArrayList(); + targetCollection.add(234); + targetCollection.add("both"); + + DbObject targetDbObj = new DbObjectWithCollection("right", targetCollection); + DbProperty targetCollProp = new DbProperty(targetDbObj, "collection"); + + + DbValidator validator = mock(DbValidator.class); + when(validator.validates("collection")).thenReturn(true); + refDbObj.getValidators().add(validator); + + comparisonUtils.compareSimpleCollections(refCollProp, targetCollProp, ctx); + + + // No information should be reported... + verify(differences, never()).add( + Where.IN_BOTH_NO_DIFFERENCE, + dbPropForValue(refDbObj, "collection[1]", "both"), + dbPropForValue(targetDbObj, "collection[1]", "both")); + + verify(differences, never()).add( + Where.ONLY_IN_REFERENCE, + dbPropForValue(refDbObj, "collection[0]", 123), + dbPropForValue(targetDbObj, "collection", targetCollection)); + + verify(differences, never()).add( + Where.ONLY_IN_TARGET, + dbPropForValue(refDbObj, "collection", refCollection), + dbPropForValue(targetDbObj, "collection[0]", 234)); + } + + @Test + public void objectIsNotComparedWhenValidatorTakesResponsibility() + { + DbObject db1 = new DatabaseObject("db1"); + DbObject db2 = new DatabaseObject("db2"); + DbObject db3 = new DatabaseObject("db3"); + DbObject db4 = new DatabaseObject("db4"); + + Collection reference = new ArrayList(); + Collections.addAll(reference, db1, db3); + + Collection target = new ArrayList(); + Collections.addAll(target, db2, db4); + + DbValidator validator = mock(DbValidator.class); + when(validator.validatesFullObject()).thenReturn(true); + db1.getValidators().add(validator); + db2.getValidators().add(validator); + + comparisonUtils.compareCollections(reference, target, ctx); + + verify(differences, never()).add(Where.ONLY_IN_REFERENCE, new DbProperty(db1), null); + verify(differences, never()).add(Where.ONLY_IN_TARGET, null, new DbProperty(db2)); + verify(differences).add(Where.ONLY_IN_REFERENCE, new DbProperty(db3), null); + verify(differences).add(Where.ONLY_IN_TARGET, null, new DbProperty(db4)); + } + + // Check that two 'simple' collections (i.e. composed of non DbObject objects) + // must have the same items at the same indexes for those items to qualify as being in both + // collections with no difference. This is important for, e.g. column orderings in + // indexes or primary keys. + @Test + public void orderingFaultsWhenCollectionsSameSize() + { + Collection left = new ArrayList(); + Collections.addAll(left, 0, "one", 2, "3", "4_this", 5, "6_this", "seven"); + DbObject leftDbObj = new DbObjectWithCollection("left", left); + DbProperty leftCollProp = new DbProperty(leftDbObj, "collection"); + + Collection right = new ArrayList(); + Collections.addAll(right, 0, "one", 2, "3", "4_that", 5, "6_that", "seven"); + DbObject rightDbObj = new DbObjectWithCollection("right", right); + DbProperty rightCollProp = new DbProperty(rightDbObj, "collection"); + + comparisonUtils.compareSimpleOrderedLists(leftCollProp, rightCollProp, ctx); + + + verify(differences).add( + Where.IN_BOTH_NO_DIFFERENCE, + dbPropForValue(leftDbObj, "collection[0]", 0), + dbPropForValue(rightDbObj, "collection[0]", 0)); + verify(differences).add( + Where.IN_BOTH_NO_DIFFERENCE, + dbPropForValue(leftDbObj, "collection[1]", "one"), + dbPropForValue(rightDbObj, "collection[1]", "one")); + verify(differences).add( + Where.IN_BOTH_NO_DIFFERENCE, + dbPropForValue(leftDbObj, "collection[2]", 2), + dbPropForValue(rightDbObj, "collection[2]", 2)); + verify(differences).add( + Where.IN_BOTH_NO_DIFFERENCE, + dbPropForValue(leftDbObj, "collection[3]", "3"), + dbPropForValue(rightDbObj, "collection[3]", "3")); + verify(differences).add( + Where.IN_BOTH_BUT_DIFFERENCE, + dbPropForValue(leftDbObj, "collection[4]", "4_this"), + dbPropForValue(rightDbObj, "collection[4]", "4_that")); + verify(differences).add( + Where.IN_BOTH_NO_DIFFERENCE, + dbPropForValue(leftDbObj, "collection[5]", 5), + dbPropForValue(rightDbObj, "collection[5]", 5)); + verify(differences).add( + Where.IN_BOTH_BUT_DIFFERENCE, + dbPropForValue(leftDbObj, "collection[6]", "6_this"), + dbPropForValue(rightDbObj, "collection[6]", "6_that")); + verify(differences).add( + Where.IN_BOTH_NO_DIFFERENCE, + dbPropForValue(leftDbObj, "collection[7]", "seven"), + dbPropForValue(rightDbObj, "collection[7]", "seven")); + } + + @Test + public void orderingFaultsWhenReferenceCollectionLonger() + { + Collection reference = new ArrayList(); + Collections.addAll(reference, "a", "z", "x", "1", "2"); + DbObject refDbObj = new DbObjectWithCollection("reference", reference); + DbProperty refCollProp = new DbProperty(refDbObj, "collection"); + + Collection target = new ArrayList(); + Collections.addAll(target, "a", "Q", "x"); + DbObject targetDbObj = new DbObjectWithCollection("target", target); + DbProperty targetCollProp = new DbProperty(targetDbObj, "collection"); + + comparisonUtils.compareSimpleOrderedLists(refCollProp, targetCollProp, ctx); + + + verify(differences).add( + Where.IN_BOTH_NO_DIFFERENCE, + dbPropForValue(refDbObj, "collection[0]", "a"), + dbPropForValue(targetDbObj, "collection[0]", "a")); + verify(differences).add( + Where.IN_BOTH_BUT_DIFFERENCE, + dbPropForValue(refDbObj, "collection[1]", "z"), + dbPropForValue(targetDbObj, "collection[1]", "Q")); + verify(differences).add( + Where.IN_BOTH_NO_DIFFERENCE, + dbPropForValue(refDbObj, "collection[2]", "x"), + dbPropForValue(targetDbObj, "collection[2]", "x")); + verify(differences).add( + Where.ONLY_IN_REFERENCE, + dbPropForValue(refDbObj, "collection[3]", "1"), + null); + verify(differences).add( + Where.ONLY_IN_REFERENCE, + dbPropForValue(refDbObj, "collection[4]", "2"), + null); + } + + @Test + public void orderingFaultsWhenTargetCollectionLonger() + { + Collection reference = new ArrayList(); + Collections.addAll(reference, "a", "z", "x"); + DbObject refDbObj = new DbObjectWithCollection("reference", reference); + DbProperty refCollProp = new DbProperty(refDbObj, "collection"); + + Collection target = new ArrayList(); + Collections.addAll(target, "a", "Q", "x", "1", "2"); + DbObject targetDbObj = new DbObjectWithCollection("target", target); + DbProperty targetCollProp = new DbProperty(targetDbObj, "collection"); + + comparisonUtils.compareSimpleOrderedLists(refCollProp, targetCollProp, ctx); + + + verify(differences).add( + Where.IN_BOTH_NO_DIFFERENCE, + dbPropForValue(refDbObj, "collection[0]", "a"), + dbPropForValue(targetDbObj, "collection[0]", "a")); + verify(differences).add( + Where.IN_BOTH_BUT_DIFFERENCE, + dbPropForValue(refDbObj, "collection[1]", "z"), + dbPropForValue(targetDbObj, "collection[1]", "Q")); + verify(differences).add( + Where.IN_BOTH_NO_DIFFERENCE, + dbPropForValue(refDbObj, "collection[2]", "x"), + dbPropForValue(targetDbObj, "collection[2]", "x")); + verify(differences).add( + Where.ONLY_IN_TARGET, + null, + dbPropForValue(targetDbObj, "collection[3]", "1")); + verify(differences).add( + Where.ONLY_IN_TARGET, + null, + dbPropForValue(targetDbObj, "collection[4]", "2")); + } @Test public void compareSimpleCollections() @@ -164,50 +377,42 @@ public class DefaultComparisonUtilsTest DbObject rightDbObj = new DbObjectWithCollection("right", rightCollection); DbProperty rightCollProp = new DbProperty(rightDbObj, "collection"); - comparisonUtils.compareSimpleCollections(leftCollProp, rightCollProp, ctx, Strength.WARN); + comparisonUtils.compareSimpleCollections(leftCollProp, rightCollProp, ctx); verify(differences).add( Where.IN_BOTH_NO_DIFFERENCE, dbPropForValue(leftDbObj, "collection[0]", 123), - dbPropForValue(rightDbObj, "collection[0]", 123), - Strength.WARN); + dbPropForValue(rightDbObj, "collection[0]", 123)); verify(differences).add( Where.IN_BOTH_NO_DIFFERENCE, dbPropForValue(leftDbObj, "collection[1]", "both"), - dbPropForValue(rightDbObj, "collection[4]", "both"), - Strength.WARN); + dbPropForValue(rightDbObj, "collection[4]", "both")); verify(differences).add( Where.IN_BOTH_NO_DIFFERENCE, dbPropForValue(leftDbObj, "collection[2]", subCollectionLeft), - dbPropForValue(rightDbObj, "collection[2]", subCollectionRight), - Strength.WARN); + dbPropForValue(rightDbObj, "collection[2]", subCollectionRight)); verify(differences).add( Where.ONLY_IN_REFERENCE, dbPropForValue(leftDbObj, "collection[3]", 456), - dbPropForValue(rightDbObj, "collection", rightCollection), - Strength.WARN); + dbPropForValue(rightDbObj, "collection", rightCollection)); verify(differences).add( Where.ONLY_IN_REFERENCE, dbPropForValue(leftDbObj, "collection[4]", "left only"), - dbPropForValue(rightDbObj, "collection", rightCollection), - Strength.WARN); + dbPropForValue(rightDbObj, "collection", rightCollection)); verify(differences).add( Where.ONLY_IN_TARGET, dbPropForValue(leftDbObj, "collection", leftCollection), - dbPropForValue(rightDbObj, "collection[1]", 789), - Strength.WARN); + dbPropForValue(rightDbObj, "collection[1]", 789)); verify(differences).add( Where.ONLY_IN_TARGET, dbPropForValue(leftDbObj, "collection", leftCollection), - dbPropForValue(rightDbObj, "collection[3]", "right only"), - Strength.WARN); + dbPropForValue(rightDbObj, "collection[3]", "right only")); verify(differences).add( Where.ONLY_IN_TARGET, dbPropForValue(leftDbObj, "collection", leftCollection), - dbPropForValue(rightDbObj, "collection[5]", "one more right only"), - Strength.WARN); + dbPropForValue(rightDbObj, "collection[5]", "one more right only")); } private DbProperty dbPropForValue(DbObject obj, String propName, Object propValue) @@ -273,7 +478,7 @@ public class DefaultComparisonUtilsTest } @Override - protected void doDiff(DbObject right, DiffContext ctx, Strength strength) + protected void doDiff(DbObject right, DiffContext ctx) { DbProperty leftProp = new DbProperty(this); DbProperty rightProp = new DbProperty(right); diff --git a/source/java/org/alfresco/util/schemacomp/Difference.java b/source/java/org/alfresco/util/schemacomp/Difference.java index 47fd428dbc..7c53229722 100644 --- a/source/java/org/alfresco/util/schemacomp/Difference.java +++ b/source/java/org/alfresco/util/schemacomp/Difference.java @@ -38,13 +38,6 @@ public final class Difference extends Result public Difference(Where where, DbProperty left, DbProperty right) { - this(where, left, right, null); - } - - public Difference(Where where, DbProperty left, DbProperty right, Strength strength) - { - super(null); - // Sanity check parameters if (left == null && right == null) { @@ -113,7 +106,6 @@ public final class Difference extends Result @Override public String toString() { - return "Difference [where=" + this.where + ", left=" + this.left + ", right=" + this.right - + ", strength=" + this.strength + "]"; + return "Difference [where=" + this.where + ", left=" + this.left + ", right=" + this.right + "]"; } } diff --git a/source/java/org/alfresco/util/schemacomp/ExportDb.java b/source/java/org/alfresco/util/schemacomp/ExportDb.java index 27ef80bec2..0b4c84392b 100644 --- a/source/java/org/alfresco/util/schemacomp/ExportDb.java +++ b/source/java/org/alfresco/util/schemacomp/ExportDb.java @@ -25,9 +25,12 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.Map; import java.util.TreeMap; +import java.util.logging.Logger; import javax.sql.DataSource; +import org.alfresco.service.descriptor.Descriptor; +import org.alfresco.service.descriptor.DescriptorService; import org.alfresco.util.PropertyCheck; import org.alfresco.util.schemacomp.model.Column; import org.alfresco.util.schemacomp.model.ForeignKey; @@ -36,10 +39,14 @@ import org.alfresco.util.schemacomp.model.PrimaryKey; import org.alfresco.util.schemacomp.model.Schema; import org.alfresco.util.schemacomp.model.Sequence; import org.alfresco.util.schemacomp.model.Table; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.hibernate.dialect.Dialect; import org.hibernate.dialect.TypeNames; import org.springframework.context.ApplicationContext; +import com.sun.crypto.provider.DESCipher; + /** * Exports a database schema to an in-memory {@link Schema} object. @@ -57,17 +64,23 @@ public class ExportDb /** The object graph we're building */ private Schema schema; + /** What type of DBMS are we running? */ private Dialect dialect; + /** Used to gain the repository's schema version */ + private DescriptorService descriptorService; + /** Only top-level tables starting with namePrefix will be exported, set to empty string for all objects */ private String namePrefix = "alf_"; - + + private final static Log log = LogFactory.getLog(ExportDb.class); public ExportDb(ApplicationContext context) { this((DataSource) context.getBean("dataSource"), - (Dialect) context.getBean("dialect")); + (Dialect) context.getBean("dialect"), + (DescriptorService) context.getBean("descriptorComponent")); } @@ -77,10 +90,11 @@ public class ExportDb * @param connection the database connection to use for metadata queries * @param dialect the Hibernate dialect */ - public ExportDb(final DataSource dataSource, final Dialect dialect) + public ExportDb(final DataSource dataSource, final Dialect dialect, DescriptorService descriptorService) { this.dataSource = dataSource; this.dialect = dialect; + this.descriptorService = descriptorService; init(); } @@ -150,13 +164,17 @@ public class ExportDb public void execute() { PropertyCheck.mandatory(this, "dataSource", dataSource); + PropertyCheck.mandatory(this, "dialect", dialect); + PropertyCheck.mandatory(this, "descriptorService", descriptorService); Connection connection = null; try { connection = dataSource.getConnection(); connection.setAutoCommit(false); - execute(connection); + Descriptor descriptor = descriptorService.getServerDescriptor(); + int schemaVersion = descriptor.getSchema(); + execute(connection, schemaVersion); } catch (Exception e) { @@ -180,15 +198,15 @@ public class ExportDb - private void execute(Connection con) throws Exception + private void execute(Connection con, int schemaVersion) throws Exception { final DatabaseMetaData dbmd = con.getMetaData(); String schemaName = getSchemaName(dbmd); - schema = new Schema(schemaName); + schema = new Schema(schemaName, namePrefix, schemaVersion); - final ResultSet tables = dbmd.getTables(null, schemaName, namePrefixFilter(), new String[] + final ResultSet tables = dbmd.getTables(null, schemaName, namePrefixFilter(dbmd), new String[] { "TABLE", "VIEW", "SEQUENCE" }); @@ -358,7 +376,7 @@ public class ExportDb while (schemas.next()) { final String thisSchema = schemas.getString("TABLE_SCHEM"); - if (thisSchema.equals(dbmd.getUserName()) || thisSchema.equalsIgnoreCase("dbo")) + if (thisSchema.equalsIgnoreCase(dbmd.getUserName()) || thisSchema.equalsIgnoreCase("dbo")) { schemaName = thisSchema; break; @@ -452,9 +470,19 @@ public class ExportDb return this.namePrefix; } - private String namePrefixFilter() + private String namePrefixFilter(DatabaseMetaData dbmd) throws SQLException { - return namePrefix + "%"; + String filter = namePrefix + "%"; + // Make sure the filter works for the particular DBMS. + if (dbmd.storesLowerCaseIdentifiers() || dbmd.storesLowerCaseQuotedIdentifiers()) + { + filter = filter.toLowerCase(); + } + else + { + filter = filter.toUpperCase(); + } + return filter; } diff --git a/source/java/org/alfresco/util/schemacomp/MultiFileDumper.java b/source/java/org/alfresco/util/schemacomp/MultiFileDumper.java new file mode 100644 index 0000000000..66e3d0f79b --- /dev/null +++ b/source/java/org/alfresco/util/schemacomp/MultiFileDumper.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2005-2012 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.util.schemacomp; + +import java.io.File; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.List; + +import org.alfresco.util.ParameterCheck; +import org.alfresco.util.TempFileProvider; +import org.springframework.context.ApplicationContext; + +/** + * Given a set of database object prefixes (e.g. "alf_", "avm_", "jbpm_") and + * a file name template (e.g. "AlfrescoSchema-MySQL-{0}-") will produce a set of files, + * one per database object prefix of the form: + *
+ *   AlfrescoSchema-MySQL-alf_-2334829.xml
+ * 
+ * Where the database object prefix is substituted for parameter {0} and the random number + * is produced by the File.createTempFile() method. The suffix .xml is always used. + * + * @author Matt Ward + */ +public class MultiFileDumper +{ + private final String[] dbPrefixes; + private final File directory; + private final String fileNameTemplate; + private final DbToXMLFactory dbToXMLFactory; + private final static String fileNameSuffix = ".xml"; + public final static String[] DEFAULT_PREFIXES = new String[] { "alf_", "avm_", "jbpm_", "act_" }; + + + /** + * Constructor with all available arguments. + * + * @param dbPrefixes + * @param directory + * @param fileNameTemplate + * @param dbToXMLFactory + */ + public MultiFileDumper(String[] dbPrefixes, File directory, String fileNameTemplate, DbToXMLFactory dbToXMLFactory) + { + ParameterCheck.mandatory("dbPrefixes", dbPrefixes); + ParameterCheck.mandatory("directory", directory); + ParameterCheck.mandatory("fileNameTemplate", fileNameTemplate); + ParameterCheck.mandatory("dbToXMLFactory", dbToXMLFactory); + if (dbPrefixes.length == 0) + { + throw new IllegalArgumentException("At least one database object prefix is required."); + } + + this.dbPrefixes = dbPrefixes; + this.directory = directory; + this.fileNameTemplate = fileNameTemplate; + this.dbToXMLFactory = dbToXMLFactory; + } + + + + /** + * Construct a MultiFileDumper with the {@link MultiFileDumper#DEFAULT_PREFIXES}. + * + * @param directory + * @param fileNameTemplate + * @param dbToXMLFactory + */ + public MultiFileDumper(File directory, String fileNameTemplate, DbToXMLFactory dbToXMLFactory) + { + this(DEFAULT_PREFIXES, directory, fileNameTemplate, dbToXMLFactory); + } + + + public List dumpFiles() + { + List files = new ArrayList(dbPrefixes.length); + + for (String dbPrefix : dbPrefixes) + { + String fileNamePrefix = getFileNamePrefix(dbPrefix); + File outputFile = TempFileProvider.createTempFile(fileNamePrefix, fileNameSuffix, directory); + files.add(outputFile); + DbToXML dbToXML = dbToXMLFactory.create(outputFile, dbPrefix); + dbToXML.execute(); + } + + return files; + } + + + private String getFileNamePrefix(String dbPrefix) + { + MessageFormat formatter = new MessageFormat(fileNameTemplate); + return formatter.format(new Object[] { dbPrefix }); + } + + public interface DbToXMLFactory + { + DbToXML create(File outputFile, String dbPrefix); + } + + public static class DbToXMLFactoryImpl implements DbToXMLFactory + { + private ApplicationContext ctx; + + public DbToXMLFactoryImpl(ApplicationContext ctx) + { + this.ctx = ctx; + } + + @Override + public DbToXML create(File outputFile, String dbPrefix) + { + return new DbToXML(ctx, outputFile, dbPrefix); + } + } +} diff --git a/source/java/org/alfresco/util/schemacomp/MultiFileDumperTest.java b/source/java/org/alfresco/util/schemacomp/MultiFileDumperTest.java new file mode 100644 index 0000000000..f84d8e40ba --- /dev/null +++ b/source/java/org/alfresco/util/schemacomp/MultiFileDumperTest.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2005-2012 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.util.schemacomp; + + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.argThat; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.alfresco.util.TempFileProvider; +import org.alfresco.util.schemacomp.MultiFileDumper.DbToXMLFactory; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentMatcher; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +/** + * Tests for the MultiFileDumper class. + * + * @author Matt Ward + */ +@RunWith(MockitoJUnitRunner.class) +public class MultiFileDumperTest +{ + private @Mock DbToXMLFactory dbToXMLFactory; + private @Mock DbToXML dbToXMLForA; + private @Mock DbToXML dbToXMLForB; + private @Mock DbToXML dbToXMLForC; + + + @Test(expected=IllegalArgumentException.class) + public void exceptionThrownWhenZeroPrefixesUsed() + { + // Shouldn't be able to construct a dumper with no prefixes to dump. + new MultiFileDumper(new String[] {}, TempFileProvider.getTempDir(), "", dbToXMLFactory); + } + + @Test(expected=IllegalArgumentException.class) + public void exceptionThrownWhenNullPrefixListUsed() + { + // Shouldn't be able to construct a dumper with no prefixes to dump. + new MultiFileDumper(null, TempFileProvider.getTempDir(), "", dbToXMLFactory); + } + + + @Test + public void canDumpSchemaToFiles() + { + String[] prefixes = new String[] { "a_", "b_", "c_" }; + File directory = TempFileProvider.getTempDir(); + String fileNamePattern = "SchemaDump-MySQL-{0}-"; + + MultiFileDumper dumper = new MultiFileDumper(prefixes, directory, fileNamePattern, dbToXMLFactory); + + when(dbToXMLFactory.create(argThat(isFileNameStartingWith("SchemaDump-MySQL-a_-")), eq("a_"))). + thenReturn(dbToXMLForA); + when(dbToXMLFactory.create(argThat(isFileNameStartingWith("SchemaDump-MySQL-b_-")), eq("b_"))). + thenReturn(dbToXMLForB); + when(dbToXMLFactory.create(argThat(isFileNameStartingWith("SchemaDump-MySQL-c_-")), eq("c_"))). + thenReturn(dbToXMLForC); + + + List files = dumper.dumpFiles(); + Iterator it = files.iterator(); + assertPathCorrect("SchemaDump-MySQL-a_-", directory, it.next()); + assertPathCorrect("SchemaDump-MySQL-b_-", directory, it.next()); + assertPathCorrect("SchemaDump-MySQL-c_-", directory, it.next()); + + verify(dbToXMLForA).execute(); + verify(dbToXMLForB).execute(); + verify(dbToXMLForC).execute(); + } + + @Test + public void canDumpSchemaToFilesForDefaultDBPrefixes() + { + File directory = TempFileProvider.getTempDir(); + String fileNamePattern = "SchemaDump-MySQL-{0}-"; + + MultiFileDumper dumper = new MultiFileDumper(directory, fileNamePattern, dbToXMLFactory); + + Map xmlExporters = new HashMap(MultiFileDumper.DEFAULT_PREFIXES.length); + + // Each of the prefixes will be used to call DbToXMLFactory.create(...) + for (String prefix : MultiFileDumper.DEFAULT_PREFIXES) + { + DbToXML dbToXML = mock(DbToXML.class); + xmlExporters.put(prefix, dbToXML); + when(dbToXMLFactory.create(any(File.class), eq(prefix))).thenReturn(dbToXML); + } + + dumper.dumpFiles(); + + // Check that each DEFAULT_PREFIX prefix resulted in its associated DbToXML object being used. + for (DbToXML dbToXML : xmlExporters.values()) + { + verify(dbToXML).execute(); + } + } + + private ArgumentMatcher isFileNameStartingWith(String startOfName) + { + return new FileNameBeginsWith(startOfName); + } + + private static class FileNameBeginsWith extends ArgumentMatcher + { + private final String startOfName; + + public FileNameBeginsWith(String startOfName) + { + this.startOfName = startOfName; + } + + @Override + public boolean matches(Object arg) + { + if (arg != null) + { + File fileArg = (File) arg; + return fileArg.getName().startsWith(startOfName); + } + return false; + } + } + + /** + * Check that actualFile has the expected directory and file name prefix, e.g. if the actual file + * is /temp/my_file_123.xml and we call: + *
+     *    assertPathCorrect("my_file_", new File("/tmp"), actualFile)
+     * 
+ * Then the assertion should hold true. + * + * @param expectedFileNamePrefix + * @param expectedDirectory + * @param actualFile + */ + private void assertPathCorrect(String expectedFileNamePrefix, File expectedDirectory, File actualFile) + { + File expectedPath = new File(expectedDirectory, expectedFileNamePrefix); + if (!actualFile.getAbsolutePath().startsWith(expectedPath.getAbsolutePath())) + { + String failureMsg = "File path " + actualFile.getAbsolutePath() + + " does not start as expected: " + expectedPath.getAbsolutePath(); + Assert.fail(failureMsg); + } + } +} diff --git a/source/java/org/alfresco/util/schemacomp/RedundantDbObject.java b/source/java/org/alfresco/util/schemacomp/RedundantDbObject.java index fec6d90ce5..ab0a46ee48 100644 --- a/source/java/org/alfresco/util/schemacomp/RedundantDbObject.java +++ b/source/java/org/alfresco/util/schemacomp/RedundantDbObject.java @@ -37,7 +37,6 @@ public class RedundantDbObject extends Result public RedundantDbObject(DbObject dbObject, List matches) { - super(null); this.dbObject = dbObject; this.matches = matches; } diff --git a/source/java/org/alfresco/util/schemacomp/Result.java b/source/java/org/alfresco/util/schemacomp/Result.java index 6bb2184195..883e8da9fe 100644 --- a/source/java/org/alfresco/util/schemacomp/Result.java +++ b/source/java/org/alfresco/util/schemacomp/Result.java @@ -24,26 +24,7 @@ package org.alfresco.util.schemacomp; * @author Matt Ward */ public abstract class Result -{ - public enum Strength { WARN, ERROR }; - protected final Strength strength; - - /** - * @param strength - */ - public Result(Strength strength) - { - this.strength = (strength != null ? strength : Strength.ERROR); - } - - /** - * @return the strength - */ - public Strength getStrength() - { - return this.strength; - } - +{ /** * A loggable message to describe the comparison result. Default implementation * delegates to toString() but this should generally be overridden as toString() diff --git a/source/java/org/alfresco/util/schemacomp/Results.java b/source/java/org/alfresco/util/schemacomp/Results.java index 84bf79cc58..8739a7a500 100644 --- a/source/java/org/alfresco/util/schemacomp/Results.java +++ b/source/java/org/alfresco/util/schemacomp/Results.java @@ -23,7 +23,6 @@ import java.util.Iterator; import java.util.List; import org.alfresco.util.schemacomp.Difference.Where; -import org.alfresco.util.schemacomp.Result.Strength; /** * Collects differences so that tools can report on or respond to differences between database schemas. @@ -44,23 +43,15 @@ public class Results implements Iterable * @param where The type of difference, see {@link Where} * @param left Left value, or null if the item appears in the right, but not left schema. * @param right Right value, or null if the item appears in the left, but not right schema. - * @param strength The Result.Strength of the difference, e.g. WARN or ERROR. */ - public void add(Where where, DbProperty left, DbProperty right, Strength strength) + public void add(Where where, DbProperty left, DbProperty right) { if (where != Where.IN_BOTH_NO_DIFFERENCE || reportNonDifferences) { - Difference result = new Difference(where, left, right, strength); + Difference result = new Difference(where, left, right); items.add(result); } } - - - public void add(Where where, DbProperty left, DbProperty right) - { - add(where, left, right, null); - } - public void add(Result result) { diff --git a/source/java/org/alfresco/util/schemacomp/SchemaCompTestSuite.java b/source/java/org/alfresco/util/schemacomp/SchemaCompTestSuite.java index dfc95f2f26..10c8860105 100644 --- a/source/java/org/alfresco/util/schemacomp/SchemaCompTestSuite.java +++ b/source/java/org/alfresco/util/schemacomp/SchemaCompTestSuite.java @@ -35,11 +35,12 @@ import org.junit.runners.Suite; DefaultComparisonUtilsTest.class, DifferenceTest.class, ExportDbTest.class, + MultiFileDumperTest.class, RedundantDbObjectTest.class, SchemaComparatorTest.class, + SchemaToXMLTest.class, ValidationResultTest.class, ValidatingVisitorTest.class, - SchemaToXMLTest.class, XMLToSchemaTest.class }) public class SchemaCompTestSuite diff --git a/source/java/org/alfresco/util/schemacomp/SchemaComparator.java b/source/java/org/alfresco/util/schemacomp/SchemaComparator.java index bb992eafa3..c417d111ac 100644 --- a/source/java/org/alfresco/util/schemacomp/SchemaComparator.java +++ b/source/java/org/alfresco/util/schemacomp/SchemaComparator.java @@ -18,7 +18,6 @@ */ package org.alfresco.util.schemacomp; -import org.alfresco.util.schemacomp.Result.Strength; import org.alfresco.util.schemacomp.model.Schema; import org.apache.xml.serialize.LineSeparator; import org.hibernate.dialect.Dialect; @@ -64,7 +63,7 @@ public class SchemaComparator */ private void compare() { - referenceSchema.diff(targetSchema, ctx, Strength.ERROR); + referenceSchema.diff(targetSchema, ctx); } diff --git a/source/java/org/alfresco/util/schemacomp/SchemaComparatorTest.java b/source/java/org/alfresco/util/schemacomp/SchemaComparatorTest.java index 5f9e6c8f79..0162976c89 100644 --- a/source/java/org/alfresco/util/schemacomp/SchemaComparatorTest.java +++ b/source/java/org/alfresco/util/schemacomp/SchemaComparatorTest.java @@ -34,6 +34,7 @@ import java.util.Arrays; import java.util.Iterator; import org.alfresco.util.schemacomp.Difference.Where; +import org.alfresco.util.schemacomp.model.PrimaryKey; import org.alfresco.util.schemacomp.model.Schema; import org.alfresco.util.schemacomp.model.Table; import org.hibernate.dialect.Dialect; @@ -56,8 +57,8 @@ public class SchemaComparatorTest @Before public void setup() { - reference = new Schema("schema"); - target = new Schema("schema"); + reference = new Schema("schema", "alf_", 590); + target = new Schema("schema", "alf_", 590); dialect = new MySQL5InnoDBDialect(); } @@ -94,6 +95,7 @@ public class SchemaComparatorTest Iterator it = results.iterator(); + // Table table_in_reference only appears in the reference schema Difference diff = (Difference) it.next(); assertEquals(Where.ONLY_IN_REFERENCE, diff.getWhere()); @@ -104,21 +106,11 @@ public class SchemaComparatorTest // Table tbl_has_diff_pk has PK of "id" in reference and "nodeRef" in target diff = (Difference) it.next(); - assertEquals(Where.ONLY_IN_REFERENCE, diff.getWhere()); + assertEquals(Where.IN_BOTH_BUT_DIFFERENCE, diff.getWhere()); assertEquals("schema.tbl_has_diff_pk.pk_is_diff.columnNames[0]", diff.getLeft().getPath()); - assertEquals("schema.tbl_has_diff_pk.pk_is_diff.columnNames", diff.getRight().getPath()); + assertEquals("schema.tbl_has_diff_pk.pk_is_diff.columnNames[0]", diff.getRight().getPath()); assertEquals("columnNames[0]", diff.getLeft().getPropertyName()); assertEquals("id", diff.getLeft().getPropertyValue()); - assertEquals("columnNames", diff.getRight().getPropertyName()); - assertEquals(Arrays.asList("nodeRef"), diff.getRight().getPropertyValue()); - - // Table tbl_has_diff_pk has PK of "id" in reference and "nodeRef" in target - diff = (Difference) it.next(); - assertEquals(Where.ONLY_IN_TARGET, diff.getWhere()); - assertEquals("schema.tbl_has_diff_pk.pk_is_diff.columnNames", diff.getLeft().getPath()); - assertEquals("schema.tbl_has_diff_pk.pk_is_diff.columnNames[0]", diff.getRight().getPath()); - assertEquals("columnNames", diff.getLeft().getPropertyName()); - assertEquals(Arrays.asList("id"), diff.getLeft().getPropertyValue()); assertEquals("columnNames[0]", diff.getRight().getPropertyName()); assertEquals("nodeRef", diff.getRight().getPropertyValue()); @@ -141,4 +133,120 @@ public class SchemaComparatorTest assertFalse("There should be no more differences", it.hasNext()); } + + + @Test + public void pkOrderingComparedCorrectly() + { + reference = new Schema("schema", "alf_", 590); + target = new Schema("schema", "alf_", 590); + + // Reference schema's database objects. + reference.add(new Table( + reference, + "table_name", + columns("id NUMBER(10)", "nodeRef VARCHAR2(200)", "name VARCHAR2(150)"), + new PrimaryKey(null, "my_pk_name", Arrays.asList("id", "nodeRef"), Arrays.asList(1, 2)), + fkeys(), + indexes())); + + // Target schema's database objects - note different order of PK columns. + target.add(new Table( + target, + "table_name", + columns("id NUMBER(10)", "nodeRef VARCHAR2(200)", "name VARCHAR2(150)"), + new PrimaryKey(null, "my_pk_name", Arrays.asList("id", "nodeRef"), Arrays.asList(2, 1)), + fkeys(), + indexes())); + + + comparator = new SchemaComparator(reference, target, dialect); + comparator.validateAndCompare(); + + // See stdout for diagnostics dump... + dumpDiffs(comparator.getComparisonResults(), false); + dumpValidation(comparator.getComparisonResults()); + + Results results = comparator.getComparisonResults(); + Iterator it = results.iterator(); + + + Difference diff = (Difference) it.next(); + assertEquals(Where.IN_BOTH_BUT_DIFFERENCE, diff.getWhere()); + assertEquals("schema.table_name.my_pk_name.columnOrders[0]", diff.getLeft().getPath()); + assertEquals("schema.table_name.my_pk_name.columnOrders[0]", diff.getRight().getPath()); + assertEquals("columnOrders[0]", diff.getLeft().getPropertyName()); + assertEquals(1, diff.getLeft().getPropertyValue()); + assertEquals("columnOrders[0]", diff.getRight().getPropertyName()); + assertEquals(2, diff.getRight().getPropertyValue()); + + diff = (Difference) it.next(); + assertEquals(Where.IN_BOTH_BUT_DIFFERENCE, diff.getWhere()); + assertEquals("schema.table_name.my_pk_name.columnOrders[1]", diff.getLeft().getPath()); + assertEquals("schema.table_name.my_pk_name.columnOrders[1]", diff.getRight().getPath()); + assertEquals("columnOrders[1]", diff.getLeft().getPropertyName()); + assertEquals(2, diff.getLeft().getPropertyValue()); + assertEquals("columnOrders[1]", diff.getRight().getPropertyName()); + assertEquals(1, diff.getRight().getPropertyValue()); + + assertFalse("There should be no more differences", it.hasNext()); + } + + + @Test + public void indexColumnOrderingComparedCorrectly() + { + reference = new Schema("schema", "alf_", 590); + target = new Schema("schema", "alf_", 590); + + // Reference schema's database objects. + reference.add(new Table( + reference, + "table_name", + columns("id NUMBER(10)", "nodeRef VARCHAR2(200)", "name VARCHAR2(150)"), + pk("pk", "id"), + fkeys(), + indexes("index_name id nodeRef"))); + + // Target schema's database objects - note different order of index columns. + target.add(new Table( + target, + "table_name", + columns("id NUMBER(10)", "nodeRef VARCHAR2(200)", "name VARCHAR2(150)"), + pk("pk", "id"), + fkeys(), + indexes("index_name nodeRef id"))); + + + comparator = new SchemaComparator(reference, target, dialect); + comparator.validateAndCompare(); + + // See stdout for diagnostics dump... + dumpDiffs(comparator.getComparisonResults(), false); + dumpValidation(comparator.getComparisonResults()); + + Results results = comparator.getComparisonResults(); + Iterator it = results.iterator(); + + + Difference diff = (Difference) it.next(); + assertEquals(Where.IN_BOTH_BUT_DIFFERENCE, diff.getWhere()); + assertEquals("schema.table_name.index_name.columnNames[0]", diff.getLeft().getPath()); + assertEquals("schema.table_name.index_name.columnNames[0]", diff.getRight().getPath()); + assertEquals("columnNames[0]", diff.getLeft().getPropertyName()); + assertEquals("id", diff.getLeft().getPropertyValue()); + assertEquals("columnNames[0]", diff.getRight().getPropertyName()); + assertEquals("nodeRef", diff.getRight().getPropertyValue()); + + diff = (Difference) it.next(); + assertEquals(Where.IN_BOTH_BUT_DIFFERENCE, diff.getWhere()); + assertEquals("schema.table_name.index_name.columnNames[1]", diff.getLeft().getPath()); + assertEquals("schema.table_name.index_name.columnNames[1]", diff.getRight().getPath()); + assertEquals("columnNames[1]", diff.getLeft().getPropertyName()); + assertEquals("nodeRef", diff.getLeft().getPropertyValue()); + assertEquals("columnNames[1]", diff.getRight().getPropertyName()); + assertEquals("id", diff.getRight().getPropertyValue()); + + assertFalse("There should be no more differences", it.hasNext()); + } } diff --git a/source/java/org/alfresco/util/schemacomp/SchemaReferenceFileTest.java b/source/java/org/alfresco/util/schemacomp/SchemaReferenceFileTest.java index 55fbfb7c92..bb2762301e 100644 --- a/source/java/org/alfresco/util/schemacomp/SchemaReferenceFileTest.java +++ b/source/java/org/alfresco/util/schemacomp/SchemaReferenceFileTest.java @@ -64,7 +64,7 @@ public class SchemaReferenceFileTest @Test public void checkReferenceFile() { - String filePrefix = getClass().getSimpleName() + "-"; + String filePrefix = getClass().getSimpleName() + "-{0}-{1}-"; int numProblems = schemaBootstrap.validateSchema(filePrefix); if (numProblems > 0) diff --git a/source/java/org/alfresco/util/schemacomp/SchemaToXMLTest.java b/source/java/org/alfresco/util/schemacomp/SchemaToXMLTest.java index 4e9b2f4263..f9c4372dce 100644 --- a/source/java/org/alfresco/util/schemacomp/SchemaToXMLTest.java +++ b/source/java/org/alfresco/util/schemacomp/SchemaToXMLTest.java @@ -57,7 +57,7 @@ public class SchemaToXMLTest Writer writer = new StringWriter(); StreamResult out = new StreamResult(writer); - Schema schema = new Schema("alfresco"); + Schema schema = new Schema("alfresco", "my-prefix", 501); schema.add( table("node", @@ -80,7 +80,11 @@ public class SchemaToXMLTest // are performed by DbObjectXMLTransformerTest BufferedReader reader = new BufferedReader(new StringReader(writer.toString())); assertEquals("", reader.readLine()); - assertEquals("", reader.readLine()); - assertEquals(" ", reader.readLine()); + String xsd = + "xmlns=\"http://www.alfresco.org/repo/db-schema\" " + + "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " + + "xsi:schemaLocation=\"http://www.alfresco.org/repo/db-schema db-schema.xsd\""; + assertEquals("", reader.readLine()); + assertEquals(" ", reader.readLine()); } } diff --git a/source/java/org/alfresco/util/schemacomp/ValidationResult.java b/source/java/org/alfresco/util/schemacomp/ValidationResult.java index 5cdc81c5b8..72b0900ad3 100644 --- a/source/java/org/alfresco/util/schemacomp/ValidationResult.java +++ b/source/java/org/alfresco/util/schemacomp/ValidationResult.java @@ -33,12 +33,6 @@ public class ValidationResult extends Result public ValidationResult(DbProperty dbProperty, String message) { - this(dbProperty, null, message); - } - - public ValidationResult(DbProperty dbProperty, Strength strength, String message) - { - super(strength); this.dbProperty = dbProperty; this.message = message; } diff --git a/source/java/org/alfresco/util/schemacomp/XML.java b/source/java/org/alfresco/util/schemacomp/XML.java index f39d12a0b3..8586ffeeba 100644 --- a/source/java/org/alfresco/util/schemacomp/XML.java +++ b/source/java/org/alfresco/util/schemacomp/XML.java @@ -55,4 +55,6 @@ public abstract class XML public static final String ATTR_ORDER = "order"; public static final String ATTR_UNIQUE = "unique"; public static final String ATTR_CLASS = "class"; + public static final String ATTR_DB_PREFIX = "dbprefix"; + public static final String ATTR_VERSION = "version"; } diff --git a/source/java/org/alfresco/util/schemacomp/XMLToSchema.java b/source/java/org/alfresco/util/schemacomp/XMLToSchema.java index f498a8b7cc..0e7e8d9b47 100644 --- a/source/java/org/alfresco/util/schemacomp/XMLToSchema.java +++ b/source/java/org/alfresco/util/schemacomp/XMLToSchema.java @@ -209,7 +209,11 @@ public class XMLToSchema extends DefaultHandler if (qName.equals(XML.EL_SCHEMA)) { - schema = new Schema(atts.getValue(XML.ATTR_NAME)); + String name = atts.getValue(XML.ATTR_NAME); + String dbPrefix = atts.getValue(XML.ATTR_DB_PREFIX); + int version = Integer.parseInt(atts.getValue(XML.ATTR_VERSION)); + schema = new Schema(name, dbPrefix, version); + stack.push(schema); } else if (qName.equals(XML.EL_TABLE)) { diff --git a/source/java/org/alfresco/util/schemacomp/XMLToSchemaTest.java b/source/java/org/alfresco/util/schemacomp/XMLToSchemaTest.java index 923e285cdc..a0cd21ed36 100644 --- a/source/java/org/alfresco/util/schemacomp/XMLToSchemaTest.java +++ b/source/java/org/alfresco/util/schemacomp/XMLToSchemaTest.java @@ -68,6 +68,8 @@ public class XMLToSchemaTest assertNotNull("A null Schema object was returned", schema); assertNull("Schema isn't meant to have a parent", schema.getParent()); assertEquals("alfresco", schema.getName()); + assertEquals("myprefix_", schema.getDbPrefix()); + assertEquals(325, schema.getVersion()); Iterator objects = schema.iterator(); diff --git a/source/java/org/alfresco/util/schemacomp/model/AbstractDbObject.java b/source/java/org/alfresco/util/schemacomp/model/AbstractDbObject.java index 8c2ae45207..ddaab6b31a 100644 --- a/source/java/org/alfresco/util/schemacomp/model/AbstractDbObject.java +++ b/source/java/org/alfresco/util/schemacomp/model/AbstractDbObject.java @@ -19,14 +19,12 @@ package org.alfresco.util.schemacomp.model; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import org.alfresco.util.schemacomp.ComparisonUtils; import org.alfresco.util.schemacomp.DbProperty; import org.alfresco.util.schemacomp.DefaultComparisonUtils; import org.alfresco.util.schemacomp.DiffContext; -import org.alfresco.util.schemacomp.Result.Strength; import org.alfresco.util.schemacomp.Results; import org.alfresco.util.schemacomp.validator.DbValidator; @@ -39,10 +37,8 @@ public abstract class AbstractDbObject implements DbObject { private DbObject parent; private String name; - /** How differences in the name field should be reported */ - private Strength nameStrength = Strength.ERROR; protected ComparisonUtils comparisonUtils = new DefaultComparisonUtils(); - private List validators = new ArrayList(); + private final List validators = new ArrayList(); /** @@ -76,22 +72,6 @@ public abstract class AbstractDbObject implements DbObject { this.name = name; } - - /** - * @return the nameStrength - */ - public Strength getNameStrength() - { - return this.nameStrength; - } - - /** - * @param nameStrength the nameStrength to set - */ - public void setNameStrength(Strength nameStrength) - { - this.nameStrength = nameStrength; - } @Override public boolean sameAs(DbObject other) @@ -106,7 +86,8 @@ public abstract class AbstractDbObject implements DbObject } if (!this.getClass().equals(other.getClass())) { - // Objects are not the same type, even if they have the same name and parent + // Objects are not the same type, so are not the same - even if they + // do have the same name and parent. return false; } if (getName() != null && other != null && other.getName() != null) @@ -121,6 +102,7 @@ public abstract class AbstractDbObject implements DbObject { sameParent = true; } + // Same parent & same name - it must be considered the same object. return sameParent && getName().equals(other.getName()); } @@ -133,6 +115,7 @@ public abstract class AbstractDbObject implements DbObject final int prime = 31; int result = 1; result = prime * result + ((this.name == null) ? 0 : this.name.hashCode()); + result = prime * result + ((this.parent == null) ? 0 : this.parent.hashCode()); return result; } @@ -148,9 +131,14 @@ public abstract class AbstractDbObject implements DbObject if (other.name != null) return false; } else if (!this.name.equals(other.name)) return false; + if (this.parent == null) + { + if (other.parent != null) return false; + } + else if (!this.parent.equals(other.parent)) return false; return true; } - + @Override public String toString() { @@ -181,14 +169,13 @@ public abstract class AbstractDbObject implements DbObject * its diff correctly. */ @Override - public void diff(DbObject right, DiffContext ctx, Strength strength) - { + public void diff(DbObject right, DiffContext ctx) + { DbProperty leftNameProp = new DbProperty(this, "name"); DbProperty rightNameProp = new DbProperty(right, "name"); + comparisonUtils.compareSimple(leftNameProp, rightNameProp, ctx); - comparisonUtils.compareSimple(leftNameProp, rightNameProp, ctx, getNameStrength()); - - doDiff(right, ctx, strength); + doDiff(right, ctx); } @@ -211,9 +198,8 @@ public abstract class AbstractDbObject implements DbObject * * @param right * @param differences - * @param strength */ - protected void doDiff(DbObject right, DiffContext ctx, Strength strength) + protected void doDiff(DbObject right, DiffContext ctx) { } @@ -243,17 +229,32 @@ public abstract class AbstractDbObject implements DbObject @Override public void setValidators(List validators) { - if (validators == null) + this.validators.clear(); + if (validators != null) { - this.validators = Collections.emptyList(); - } - else - { - this.validators = validators; + this.validators.addAll(validators); } } - + @Override + public boolean hasValidators() + { + return getValidators().size() > 0; + } + + @Override + public boolean hasObjectLevelValidator() + { + for (DbValidator validator : getValidators()) + { + if (validator.validatesFullObject()) + { + return true; + } + } + return false; + } + @Override public String getTypeName() { diff --git a/source/java/org/alfresco/util/schemacomp/model/AbstractDbObjectTest.java b/source/java/org/alfresco/util/schemacomp/model/AbstractDbObjectTest.java index 068c25ffa0..6cee114cbb 100644 --- a/source/java/org/alfresco/util/schemacomp/model/AbstractDbObjectTest.java +++ b/source/java/org/alfresco/util/schemacomp/model/AbstractDbObjectTest.java @@ -30,7 +30,6 @@ import org.alfresco.util.schemacomp.DbObjectVisitor; import org.alfresco.util.schemacomp.DbProperty; import org.alfresco.util.schemacomp.DiffContext; import org.alfresco.util.schemacomp.Difference.Where; -import org.alfresco.util.schemacomp.Result.Strength; import org.alfresco.util.schemacomp.Results; import org.alfresco.util.schemacomp.validator.AbstractDbValidator; import org.alfresco.util.schemacomp.validator.DbValidator; @@ -64,12 +63,6 @@ public class AbstractDbObjectTest dbObject = new ConcreteDbObject("the_object"); ctx = new DiffContext(dialect, differences, null, null); } - - @Test - public void defaultNameStrength() - { - assertEquals(Strength.ERROR, dbObject.getNameStrength()); - } @Test public void sameAs() @@ -91,9 +84,8 @@ public class AbstractDbObjectTest public void diff() { ConcreteDbObject otherObject = new ConcreteDbObject("the_other_object"); - dbObject.setNameStrength(Strength.WARN); - dbObject.diff(otherObject, ctx, Strength.ERROR); + dbObject.diff(otherObject, ctx); InOrder inOrder = inOrder(differences); @@ -101,8 +93,7 @@ public class AbstractDbObjectTest inOrder.verify(differences).add( Where.IN_BOTH_BUT_DIFFERENCE, new DbProperty(dbObject, "name"), - new DbProperty(otherObject, "name"), - Strength.WARN); + new DbProperty(otherObject, "name")); // Then the doDiff() method should be processed inOrder.verify(differences).add( @@ -143,7 +134,7 @@ public class AbstractDbObjectTest } @Override - protected void doDiff(DbObject right, DiffContext ctx, Strength strength) + protected void doDiff(DbObject right, DiffContext ctx) { Results differences = ctx.getComparisonResults(); differences.add( diff --git a/source/java/org/alfresco/util/schemacomp/model/Column.java b/source/java/org/alfresco/util/schemacomp/model/Column.java index 34597d00e1..8439da885a 100644 --- a/source/java/org/alfresco/util/schemacomp/model/Column.java +++ b/source/java/org/alfresco/util/schemacomp/model/Column.java @@ -21,7 +21,6 @@ package org.alfresco.util.schemacomp.model; import org.alfresco.util.schemacomp.DbObjectVisitor; import org.alfresco.util.schemacomp.DbProperty; import org.alfresco.util.schemacomp.DiffContext; -import org.alfresco.util.schemacomp.Result.Strength; /** * Represents a column in a database table. @@ -154,7 +153,7 @@ public class Column extends AbstractDbObject } @Override - protected void doDiff(DbObject right, DiffContext ctx, Strength strength) + protected void doDiff(DbObject right, DiffContext ctx) { DbProperty thisTypeProp = new DbProperty(this, "type"); DbProperty thisNullableProp = new DbProperty(this, "nullable"); @@ -178,28 +177,4 @@ public class Column extends AbstractDbObject { visitor.visit(this); } - - @Override - public boolean sameAs(DbObject other) - { - if (other == null) - { - return false; - } - if (this == other) - { - return true; - } - if (getClass().equals(other.getClass())) - { - if (getName() != null && other.getName() != null) - { - if (getParent() != null && other.getParent() != null && getParent().sameAs(other.getParent())) - { - return getName().equals(other.getName()); - } - } - } - return false; - } } diff --git a/source/java/org/alfresco/util/schemacomp/model/DbObject.java b/source/java/org/alfresco/util/schemacomp/model/DbObject.java index 0a84777e9b..4b001e3974 100644 --- a/source/java/org/alfresco/util/schemacomp/model/DbObject.java +++ b/source/java/org/alfresco/util/schemacomp/model/DbObject.java @@ -22,7 +22,6 @@ import java.util.List; import org.alfresco.util.schemacomp.DbObjectVisitor; import org.alfresco.util.schemacomp.DiffContext; -import org.alfresco.util.schemacomp.Result.Strength; import org.alfresco.util.schemacomp.Results; import org.alfresco.util.schemacomp.validator.DbValidator; @@ -63,7 +62,7 @@ public interface DbObject * @param right The object to compare against. * @param ctx The DiffContext */ - void diff(DbObject right, DiffContext ctx, Strength strength); + void diff(DbObject right, DiffContext ctx); /** @@ -106,6 +105,21 @@ public interface DbObject */ void setValidators(List validators); + /** + * Does the database object have any validators associated with it? + * + * @return true if there are one or more validators. + */ + boolean hasValidators(); + + /** + * Is there at least one validator that assumes object-level validation + * that removes the requirement for reporting of differences for this object? + * + * @return + */ + boolean hasObjectLevelValidator(); + /** * Type name, e.g. "column", "foreign key" * diff --git a/source/java/org/alfresco/util/schemacomp/model/DbObjectTestBase.java b/source/java/org/alfresco/util/schemacomp/model/DbObjectTestBase.java index 5891e5efa6..e8e1f11e98 100644 --- a/source/java/org/alfresco/util/schemacomp/model/DbObjectTestBase.java +++ b/source/java/org/alfresco/util/schemacomp/model/DbObjectTestBase.java @@ -27,7 +27,6 @@ import org.alfresco.util.schemacomp.ComparisonUtils; import org.alfresco.util.schemacomp.DbObjectVisitor; import org.alfresco.util.schemacomp.DbProperty; import org.alfresco.util.schemacomp.DiffContext; -import org.alfresco.util.schemacomp.Result.Strength; import org.alfresco.util.schemacomp.Results; import org.hibernate.dialect.Dialect; import org.junit.Before; @@ -87,14 +86,13 @@ public abstract class DbObjectTestBase thatObject.setComparisonUtils(comparisonUtils); // Invoke the method under test - thisObject.diff(thatObject, ctx, Strength.ERROR); + thisObject.diff(thatObject, ctx); // The name of the object should be diffed inOrder.verify(comparisonUtils).compareSimple( new DbProperty(thisObject, "name"), new DbProperty(thatObject, "name"), - ctx, - thisObject.getNameStrength()); + ctx); // Then the doDiff() method should be processed... doDiffTests(); diff --git a/source/java/org/alfresco/util/schemacomp/model/ForeignKey.java b/source/java/org/alfresco/util/schemacomp/model/ForeignKey.java index d1c378a37c..bec162cfb8 100644 --- a/source/java/org/alfresco/util/schemacomp/model/ForeignKey.java +++ b/source/java/org/alfresco/util/schemacomp/model/ForeignKey.java @@ -21,7 +21,6 @@ package org.alfresco.util.schemacomp.model; import org.alfresco.util.schemacomp.DbObjectVisitor; import org.alfresco.util.schemacomp.DbProperty; import org.alfresco.util.schemacomp.DiffContext; -import org.alfresco.util.schemacomp.Result.Strength; /** @@ -145,7 +144,7 @@ public class ForeignKey extends AbstractDbObject @Override - protected void doDiff(DbObject right, DiffContext ctx, Strength strength) + protected void doDiff(DbObject right, DiffContext ctx) { ForeignKey thatFK = (ForeignKey) right; comparisonUtils.compareSimple( @@ -173,4 +172,37 @@ public class ForeignKey extends AbstractDbObject { return "foreign key"; } + + @Override + public boolean sameAs(DbObject other) + { + if (other == null) + { + return false; + } + if (!getClass().equals(other.getClass())) + { + return false; + } + + if ((getParent() != null && getParent().sameAs(other.getParent()))) + { + ForeignKey otherFK = (ForeignKey) other; + if (!getLocalColumn().equals(otherFK.getLocalColumn())) + { + return false; + } + if (!getTargetTable().equals(otherFK.getTargetTable())) + { + return false; + } + if (!getTargetColumn().equals(otherFK.getTargetColumn())) + { + return false; + } + return true; + } + + return false; + } } diff --git a/source/java/org/alfresco/util/schemacomp/model/ForeignKeyTest.java b/source/java/org/alfresco/util/schemacomp/model/ForeignKeyTest.java index 42f342faed..14e0f3c924 100644 --- a/source/java/org/alfresco/util/schemacomp/model/ForeignKeyTest.java +++ b/source/java/org/alfresco/util/schemacomp/model/ForeignKeyTest.java @@ -19,6 +19,7 @@ package org.alfresco.util.schemacomp.model; +import static org.junit.Assert.*; import static org.mockito.Mockito.verify; import org.alfresco.util.schemacomp.DbProperty; @@ -33,11 +34,12 @@ import org.junit.Test; public class ForeignKeyTest extends DbObjectTestBase { private ForeignKey thisFK, thatFK; - + private Table parent; @Before public void setUp() throws Exception { + parent = new Table("parent"); thisFK = new ForeignKey(null, "this_fk", "local_col", "target_table", "target_col"); thatFK = new ForeignKey(null, "that_fk", "local_col", "target_table", "target_col"); } @@ -81,4 +83,40 @@ public class ForeignKeyTest extends DbObjectTestBase verify(visitor).visit(thisFK); } + + @Test + public void sameAs() + { + // FKs are the same if they have all the same properties + thisFK = new ForeignKey(parent, "the_fk", "local_col", "target_table", "target_col"); + thatFK = new ForeignKey(parent, "the_fk", "local_col", "target_table", "target_col"); + assertTrue("FKs should be considered the same", thisFK.sameAs(thatFK)); + + // FKs are the same even if they have different names (but all other properties are the same) + thisFK = new ForeignKey(parent, "the_fk", "local_col", "target_table", "target_col"); + thatFK = new ForeignKey(parent, "different_name", "local_col", "target_table", "target_col"); + assertTrue("FKs should be considered the same", thisFK.sameAs(thatFK)); + + // Two references to the same FK are the same of course + assertTrue("FKs should be considered the same", thisFK.sameAs(thisFK)); + + // A null is never the same + assertFalse("FKs should be considered the different", thisFK.sameAs(null)); + + thisFK = new ForeignKey(parent, "the_fk", "local_col", "target_table", "target_col"); + thatFK = new ForeignKey(new Table("different_parent"), "the_fk", "local_col", "target_table", "target_col"); + assertFalse("FKs should be different: parents are different.", thisFK.sameAs(thatFK)); + + thisFK = new ForeignKey(parent, "the_fk", "local_col", "target_table", "target_col"); + thatFK = new ForeignKey(parent, "the_fk", "local_col2", "target_table", "target_col"); + assertFalse("FKs have different local columns.", thisFK.sameAs(thatFK)); + + thisFK = new ForeignKey(parent, "the_fk", "local_col", "target_table", "target_col"); + thatFK = new ForeignKey(parent, "the_fk", "local_col", "target_table2", "target_col"); + assertFalse("FKs have different target table.", thisFK.sameAs(thatFK)); + + thisFK = new ForeignKey(parent, "the_fk", "local_col", "target_table", "target_col"); + thatFK = new ForeignKey(parent, "the_fk", "local_col", "target_table", "target_col2"); + assertFalse("FKs have different target column.", thisFK.sameAs(thatFK)); + } } diff --git a/source/java/org/alfresco/util/schemacomp/model/Index.java b/source/java/org/alfresco/util/schemacomp/model/Index.java index 4d7f76ddea..b748b48974 100644 --- a/source/java/org/alfresco/util/schemacomp/model/Index.java +++ b/source/java/org/alfresco/util/schemacomp/model/Index.java @@ -24,7 +24,6 @@ import java.util.List; import org.alfresco.util.schemacomp.DbObjectVisitor; import org.alfresco.util.schemacomp.DbProperty; import org.alfresco.util.schemacomp.DiffContext; -import org.alfresco.util.schemacomp.Result.Strength; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -52,7 +51,6 @@ public class Index extends AbstractDbObject { super(table, name); this.columnNames.addAll(columnNames); - setNameStrength(Strength.WARN); } /** @@ -150,14 +148,15 @@ public class Index extends AbstractDbObject @Override - protected void doDiff(DbObject right, DiffContext ctx, Strength strength) + protected void doDiff(DbObject right, DiffContext ctx) { Index rightIndex = (Index) right; - comparisonUtils.compareSimpleCollections( + // DatabaseMetaData provides the columns in the correct order for the index. + // So compare as ordered collections... + comparisonUtils.compareSimpleOrderedLists( new DbProperty(this, "columnNames"), new DbProperty(rightIndex, "columnNames"), - ctx, - strength); + ctx); comparisonUtils.compareSimple( new DbProperty(this, "unique"), new DbProperty(rightIndex, "unique"), diff --git a/source/java/org/alfresco/util/schemacomp/model/IndexTest.java b/source/java/org/alfresco/util/schemacomp/model/IndexTest.java index 47e8168f3a..de3e81988b 100644 --- a/source/java/org/alfresco/util/schemacomp/model/IndexTest.java +++ b/source/java/org/alfresco/util/schemacomp/model/IndexTest.java @@ -18,7 +18,6 @@ */ package org.alfresco.util.schemacomp.model; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.verify; @@ -26,7 +25,6 @@ import static org.mockito.Mockito.verify; import java.util.Arrays; import org.alfresco.util.schemacomp.DbProperty; -import org.alfresco.util.schemacomp.Result.Strength; import org.junit.Before; import org.junit.Test; @@ -66,23 +64,15 @@ public class IndexTest extends DbObjectTestBase @Override protected void doDiffTests() { - inOrder.verify(comparisonUtils).compareSimpleCollections( + inOrder.verify(comparisonUtils).compareSimpleOrderedLists( new DbProperty(thisIndex, "columnNames"), new DbProperty(thatIndex, "columnNames"), - ctx, - Strength.ERROR); + ctx); inOrder.verify(comparisonUtils).compareSimple( new DbProperty(thisIndex, "unique"), new DbProperty(thatIndex, "unique"), ctx); } - - - @Test - public void differentNamesResultInWarningsNotErrors() - { - assertEquals("Name differences should be reported as warnings.", Strength.WARN, thisIndex.getNameStrength()); - } @Test diff --git a/source/java/org/alfresco/util/schemacomp/model/PrimaryKey.java b/source/java/org/alfresco/util/schemacomp/model/PrimaryKey.java index a7db08cabb..8906a5be1c 100644 --- a/source/java/org/alfresco/util/schemacomp/model/PrimaryKey.java +++ b/source/java/org/alfresco/util/schemacomp/model/PrimaryKey.java @@ -24,7 +24,6 @@ import java.util.List; import org.alfresco.util.schemacomp.DbObjectVisitor; import org.alfresco.util.schemacomp.DbProperty; import org.alfresco.util.schemacomp.DiffContext; -import org.alfresco.util.schemacomp.Result.Strength; /** * Primary key on a table. @@ -102,6 +101,8 @@ public class PrimaryKey extends AbstractDbObject this.columnOrders.addAll(columnOrders); } + + @Override public int hashCode() { @@ -133,20 +134,18 @@ public class PrimaryKey extends AbstractDbObject } @Override - protected void doDiff(DbObject right, DiffContext ctx, Strength strength) + protected void doDiff(DbObject right, DiffContext ctx) { checkColumnOrders(); PrimaryKey rightPK = (PrimaryKey) right; - comparisonUtils.compareSimpleCollections( + comparisonUtils.compareSimpleOrderedLists( new DbProperty(this, "columnNames"), new DbProperty(rightPK, "columnNames"), - ctx, - strength); - comparisonUtils.compareSimpleCollections( + ctx); + comparisonUtils.compareSimpleOrderedLists( new DbProperty(this, "columnOrders"), new DbProperty(rightPK, "columnOrders"), - ctx, - strength); + ctx); } @Override @@ -161,4 +160,23 @@ public class PrimaryKey extends AbstractDbObject { return "primary key"; } + + @Override + public boolean sameAs(DbObject other) + { + if (other == null) + { + return false; + } + if (!getClass().equals(other.getClass())) + { + return false; + } + if ((getParent() != null && getParent().sameAs(other.getParent()))) + { + return true; + } + + return false; + } } diff --git a/source/java/org/alfresco/util/schemacomp/model/PrimaryKeyTest.java b/source/java/org/alfresco/util/schemacomp/model/PrimaryKeyTest.java index 4b121461e2..d6fb731176 100644 --- a/source/java/org/alfresco/util/schemacomp/model/PrimaryKeyTest.java +++ b/source/java/org/alfresco/util/schemacomp/model/PrimaryKeyTest.java @@ -18,12 +18,14 @@ */ package org.alfresco.util.schemacomp.model; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.verify; import java.util.Arrays; +import java.util.List; import org.alfresco.util.schemacomp.DbProperty; -import org.alfresco.util.schemacomp.Result.Strength; import org.junit.Before; import org.junit.Test; @@ -37,20 +39,23 @@ public class PrimaryKeyTest extends DbObjectTestBase { private PrimaryKey thisPK; private PrimaryKey thatPK; - + private Table parent; + @Before public void setUp() { + parent = new Table("parent_table"); + thisPK = new PrimaryKey( - null, + parent, "this_pk", - Arrays.asList("id", "name", "age"), - Arrays.asList(2, 1, 3)); + columns("id", "name", "age"), + columnOrders(2, 1, 3)); thatPK = new PrimaryKey( - null, + parent, "that_pk", - Arrays.asList("a", "b"), - Arrays.asList(1, 2)); + columns("a", "b"), + columnOrders(1, 2)); } @Override @@ -68,16 +73,14 @@ public class PrimaryKeyTest extends DbObjectTestBase @Override protected void doDiffTests() { - inOrder.verify(comparisonUtils).compareSimpleCollections( + inOrder.verify(comparisonUtils).compareSimpleOrderedLists( new DbProperty(thisPK, "columnNames"), new DbProperty(thatPK, "columnNames"), - ctx, - Strength.ERROR); - inOrder.verify(comparisonUtils).compareSimpleCollections( + ctx); + inOrder.verify(comparisonUtils).compareSimpleOrderedLists( new DbProperty(thisPK, "columnOrders"), new DbProperty(thatPK, "columnOrders"), - ctx, - Strength.ERROR); + ctx); } @Test @@ -87,4 +90,60 @@ public class PrimaryKeyTest extends DbObjectTestBase verify(visitor).visit(thisPK); } + + @Test + public void sameAs() + { + // Same parent, same name, same columns and ordering - must be same (intended) PK + assertTrue("Primary keys should be logically equivalent", + thisPK.sameAs( + new PrimaryKey( + parent, + "this_pk", + columns("id", "name", "age"), + columnOrders(2, 1, 3)))); + + // Different names - still the same PK + assertTrue("Primary keys should be logically equivalent", + thisPK.sameAs( + new PrimaryKey(parent, "different_name", + columns("id", "name", "age"), + columnOrders(2, 1, 3)))); + + // Same object reference ("physically" same object) + assertTrue("PKs should be the same", thisPK.sameAs(thisPK)); + + // Same parent, same type, but different everything else! + assertTrue("Primary keys should be logically equivalent", + thisPK.sameAs( + new PrimaryKey(parent, "different_name", + columns("di", "eman", "ega"), + columnOrders(1, 2, 3)))); + + // Same parent, but different type + assertFalse("PKs are never the same as a non-PK", + thisPK.sameAs(new Index(parent, "wrong_type", columns()))); + + // Different parents + Table otherParent = new Table("other_parent"); + assertFalse("PKs should be considered different (different parents)", + thisPK.sameAs( + new PrimaryKey(otherParent, "this_pk", + columns("id", "name", "age"), + columnOrders(2, 1, 3)))); + + // Other PK is null + assertFalse("PKs should not be considered the same (other PK is null)", + thisPK.sameAs(null)); + } + + private List columns(String... columns) + { + return Arrays.asList(columns); + } + + private List columnOrders(Integer... columnOrders) + { + return Arrays.asList(columnOrders); + } } diff --git a/source/java/org/alfresco/util/schemacomp/model/Schema.java b/source/java/org/alfresco/util/schemacomp/model/Schema.java index 10ee6ddd6b..225d4de2a4 100644 --- a/source/java/org/alfresco/util/schemacomp/model/Schema.java +++ b/source/java/org/alfresco/util/schemacomp/model/Schema.java @@ -21,10 +21,14 @@ package org.alfresco.util.schemacomp.model; import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.regex.Pattern; +import org.alfresco.util.ParameterCheck; import org.alfresco.util.schemacomp.DbObjectVisitor; +import org.alfresco.util.schemacomp.DbProperty; import org.alfresco.util.schemacomp.DiffContext; -import org.alfresco.util.schemacomp.Result.Strength; +import org.alfresco.util.schemacomp.validator.NameValidator; +import org.alfresco.util.schemacomp.validator.SchemaVersionValidator; /** * Instances of this class represent a database schema. @@ -34,15 +38,52 @@ import org.alfresco.util.schemacomp.Result.Strength; public class Schema extends AbstractDbObject implements Iterable { protected final List objects = new ArrayList(); - + protected final String dbPrefix; + protected final int version; + /** - * Construct a schema with the given name. + * Construct a schema with the given name and no database prefix. * * @param name */ public Schema(String name) + { + this(name, "", 0); + } + + /** + * Construct a schema with the given name and database prefix. The database + * prefix specifies what filtering applies to the high-level (tables and sequences) + * objects in the schema. If for example dbPrefix is "alf_" then only tables and sequences + * whose names begin with "alf_" will be represented by this schema. Therefore any comparisons + * should be performed against another similarly filtered Schema object. + * + * @param name + * @param dbPrefix + */ + public Schema(String name, String dbPrefix, int schemaVersion) { super(null, name); + ParameterCheck.mandatory("dbPrefix", dbPrefix); + this.dbPrefix = dbPrefix; + this.version = schemaVersion; + + addDefaultValidators(); + } + + /** + * Add a set of validators that should be present by default on Schema objects. + */ + private void addDefaultValidators() + { + // We expect the user's database to have a different schema name to the reference schema. + NameValidator nameValidator = new NameValidator(); + nameValidator.setPattern(Pattern.compile(".*")); + getValidators().add(nameValidator); + + // The schema version shouldn't have to match exactly. + SchemaVersionValidator versionValidator = new SchemaVersionValidator(); + getValidators().add(versionValidator); } /** @@ -72,12 +113,30 @@ public class Schema extends AbstractDbObject implements Iterable return objects.contains(object); } + /** + * @return the dbPrefix + */ + public String getDbPrefix() + { + return this.dbPrefix; + } + + /** + * @return the version + */ + public int getVersion() + { + return this.version; + } + @Override public int hashCode() { final int prime = 31; int result = super.hashCode(); + result = prime * result + ((this.dbPrefix == null) ? 0 : this.dbPrefix.hashCode()); result = prime * result + ((this.objects == null) ? 0 : this.objects.hashCode()); + result = prime * result + this.version; return result; } @@ -88,19 +147,27 @@ public class Schema extends AbstractDbObject implements Iterable if (!super.equals(obj)) return false; if (getClass() != obj.getClass()) return false; Schema other = (Schema) obj; + if (this.dbPrefix == null) + { + if (other.dbPrefix != null) return false; + } + else if (!this.dbPrefix.equals(other.dbPrefix)) return false; if (this.objects == null) { if (other.objects != null) return false; } else if (!this.objects.equals(other.objects)) return false; + if (this.version != other.version) return false; return true; } - - + @Override - protected void doDiff(DbObject right, DiffContext ctx, Strength strength) + protected void doDiff(DbObject right, DiffContext ctx) { - Schema rightSchema = (Schema) right; + Schema rightSchema = (Schema) right; + comparisonUtils.compareSimple( + new DbProperty(this, "version"), + new DbProperty(rightSchema, "version"), ctx); comparisonUtils.compareCollections(objects, rightSchema.objects, ctx); } @@ -115,4 +182,14 @@ public class Schema extends AbstractDbObject implements Iterable child.accept(visitor); } } + + @Override + public boolean sameAs(DbObject other) + { + if (other == null || (!other.getClass().equals(getClass()))) + { + return false; + } + return true; + } } diff --git a/source/java/org/alfresco/util/schemacomp/model/SchemaTest.java b/source/java/org/alfresco/util/schemacomp/model/SchemaTest.java index 4089a1dfe8..435fbc2232 100644 --- a/source/java/org/alfresco/util/schemacomp/model/SchemaTest.java +++ b/source/java/org/alfresco/util/schemacomp/model/SchemaTest.java @@ -18,8 +18,10 @@ */ package org.alfresco.util.schemacomp.model; +import static org.junit.Assert.*; import static org.mockito.Mockito.verify; +import org.alfresco.util.schemacomp.DbProperty; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -59,6 +61,13 @@ public class SchemaTest extends DbObjectTestBase @Override protected void doDiffTests() { + // We need to be warned if comparing, for example a version 500 schema with a + // version 501 schema. + inOrder.verify(comparisonUtils).compareSimple( + new DbProperty(left, "version"), + new DbProperty(right, "version"), + ctx); + // In addition to the base class functionality, Schema.diff() compares // the DbObjects held in the other schema with its own DbObjects. inOrder.verify(comparisonUtils).compareCollections(left.objects, right.objects, ctx); @@ -81,4 +90,20 @@ public class SchemaTest extends DbObjectTestBase verify(dbo3).accept(visitor); verify(visitor).visit(left); } + + @Test + public void sameAs() + { + // We have to assume that two schemas are always the same, regardless of name, + // otherwise unless the reference schema has the same name as the target database + // all the comparisons will fail - and users can choose to install databases with any schema + // name they choose. + assertTrue("Schemas should be considered the same", left.sameAs(right)); + + // Things are always the same as themselves. + assertTrue("Schemas are the same physical object", left.sameAs(left)); + + assertFalse("A table is not the same as a schema", left.sameAs(new Table("left_schema"))); + assertFalse("null is not the same as a schema", left.sameAs(null)); + } } diff --git a/source/java/org/alfresco/util/schemacomp/model/Sequence.java b/source/java/org/alfresco/util/schemacomp/model/Sequence.java index 6b2ebd02fc..8a1898eac8 100644 --- a/source/java/org/alfresco/util/schemacomp/model/Sequence.java +++ b/source/java/org/alfresco/util/schemacomp/model/Sequence.java @@ -28,7 +28,6 @@ import org.alfresco.util.schemacomp.DbObjectVisitor; */ public class Sequence extends AbstractDbObject { - public Sequence(String name) { super(null, name); diff --git a/source/java/org/alfresco/util/schemacomp/model/Table.java b/source/java/org/alfresco/util/schemacomp/model/Table.java index f6fab0d998..9e242960d6 100644 --- a/source/java/org/alfresco/util/schemacomp/model/Table.java +++ b/source/java/org/alfresco/util/schemacomp/model/Table.java @@ -24,7 +24,6 @@ import java.util.List; import org.alfresco.util.schemacomp.DbObjectVisitor; import org.alfresco.util.schemacomp.DiffContext; -import org.alfresco.util.schemacomp.Result.Strength; /** * Instances of this class represent a database table. @@ -207,11 +206,11 @@ public class Table extends AbstractDbObject @Override - protected void doDiff(DbObject other, DiffContext ctx, Strength strength) + protected void doDiff(DbObject other, DiffContext ctx) { Table rightTable = (Table) other; comparisonUtils.compareCollections(columns, rightTable.columns, ctx); - primaryKey.diff(rightTable.primaryKey, ctx, strength); + primaryKey.diff(rightTable.primaryKey, ctx); comparisonUtils.compareCollections(foreignKeys, rightTable.foreignKeys, ctx); comparisonUtils.compareCollections(indexes, rightTable.indexes, ctx); } diff --git a/source/java/org/alfresco/util/schemacomp/model/TableTest.java b/source/java/org/alfresco/util/schemacomp/model/TableTest.java index e4fba6c23d..dafddede8b 100644 --- a/source/java/org/alfresco/util/schemacomp/model/TableTest.java +++ b/source/java/org/alfresco/util/schemacomp/model/TableTest.java @@ -24,7 +24,6 @@ import static org.mockito.Mockito.verify; import java.util.ArrayList; import java.util.List; -import org.alfresco.util.schemacomp.Result.Strength; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -84,7 +83,7 @@ public class TableTest extends DbObjectTestBase inOrder.verify(comparisonUtils).compareCollections(table.getColumns(), otherTable.getColumns(), ctx); // Check primary key - inOrder.verify(primaryKey).diff(otherTable.getPrimaryKey(), ctx, Strength.ERROR); + inOrder.verify(primaryKey).diff(otherTable.getPrimaryKey(), ctx); // Check foreign keys inOrder.verify(comparisonUtils).compareCollections( diff --git a/source/java/org/alfresco/util/schemacomp/validator/AbstractDbValidator.java b/source/java/org/alfresco/util/schemacomp/validator/AbstractDbValidator.java index 71e21a0f46..bcb3185a9e 100644 --- a/source/java/org/alfresco/util/schemacomp/validator/AbstractDbValidator.java +++ b/source/java/org/alfresco/util/schemacomp/validator/AbstractDbValidator.java @@ -21,6 +21,7 @@ package org.alfresco.util.schemacomp.validator; import java.util.HashMap; import java.util.Map; import java.util.Set; +import java.util.TreeSet; /** * Base class providing DbValidator support. @@ -30,7 +31,8 @@ import java.util.Set; public abstract class AbstractDbValidator implements DbValidator { private final Map properties = new HashMap(); - + private final Set fieldsToValidate = new TreeSet(); + @Override public void setProperty(String name, String value) { @@ -48,5 +50,27 @@ public abstract class AbstractDbValidator implements DbValidator { return properties.keySet(); } + + @Override + public boolean validates(String fieldName) + { + return fieldsToValidate.contains(fieldName); + } + @Override + public boolean validatesFullObject() + { + return false; + } + + protected void setFieldsToValidate(Set fieldsToValidate) + { + this.fieldsToValidate.clear(); + this.fieldsToValidate.addAll(fieldsToValidate); + } + + protected void addFieldToValidate(String fieldName) + { + fieldsToValidate.add(fieldName); + } } diff --git a/source/java/org/alfresco/util/schemacomp/validator/DbValidator.java b/source/java/org/alfresco/util/schemacomp/validator/DbValidator.java index 124325974e..ded895f562 100644 --- a/source/java/org/alfresco/util/schemacomp/validator/DbValidator.java +++ b/source/java/org/alfresco/util/schemacomp/validator/DbValidator.java @@ -31,11 +31,57 @@ import org.alfresco.util.schemacomp.model.DbObject; */ public interface DbValidator { + /** + * Validate the target database object (against the reference object if necessary). Store + * the validation results on the DiffContext. + * + * @param reference + * @param target + * @param ctx + */ void validate(DbObject reference, DbObject target, DiffContext ctx); + /** + * Set a property used by this validator. Validator properties provided in the schema reference + * XML files will be set on the validator using this method. + * + * @param name + * @param value + */ void setProperty(String name, String value); + /** + * Get the current value of a validator property, as set using {@link #setProperty(String, String)}. + * + * @param name + * @return + */ String getProperty(String name); + /** + * Get the complete set of validator properties in use. + * + * @return + */ Set getPropertyNames(); + + /** + * Ask whether the database object's validator is responsible for validating + * the specified field name. This only applies to simple properties - not DbObject instances + * which should provide their own validators. + * + * @param fieldName + * @return + */ + boolean validates(String fieldName); + + /** + * Asks whether the database object's validator is responsible for validating + * the entire DbObject. If true, then differences are not reported (e.g. table missing from database) + * as it is the validator's role to worry about presence. If validation and differences are required + * then report false - even if the validator works at the full object (rather than property) level. + * + * @return true if missing or unexpected database objects should not be reported by differencing logic. + */ + boolean validatesFullObject(); } diff --git a/source/java/org/alfresco/util/schemacomp/validator/IgnoreObjectValidator.java b/source/java/org/alfresco/util/schemacomp/validator/IgnoreObjectValidator.java new file mode 100644 index 0000000000..502f9ceb35 --- /dev/null +++ b/source/java/org/alfresco/util/schemacomp/validator/IgnoreObjectValidator.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2005-2012 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.util.schemacomp.validator; + +import java.util.Set; +import java.util.TreeSet; + +import org.alfresco.util.schemacomp.DiffContext; +import org.alfresco.util.schemacomp.model.DbObject; + +/** + * Allows a complete DbObject to be ignored during differencing. In other + * words if an object that has this validator applied to it is found to be + * missing from or unexpectedly in the database then that will not be reported + * as a difference. + * + * @author Matt Ward + */ +public class IgnoreObjectValidator implements DbValidator +{ + + @Override + public void validate(DbObject reference, DbObject target, DiffContext ctx) + { + // Do nothing + } + + @Override + public void setProperty(String name, String value) + { + // No validation properties + throw new UnsupportedOperationException("Properties are not supported by this validator."); + } + + @Override + public String getProperty(String name) + { + throw new IllegalArgumentException("There are no properties for a " + getClass().getName()); + } + + @Override + public Set getPropertyNames() + { + return new TreeSet(); + } + + @Override + public boolean validates(String fieldName) + { + return false; + } + + @Override + public boolean validatesFullObject() + { + // This is the important part of this implementation - to ignore an object + // we need to say this validator takes full responsibility for it. + return true; + } +} diff --git a/source/java/org/alfresco/util/schemacomp/validator/NameValidator.java b/source/java/org/alfresco/util/schemacomp/validator/NameValidator.java index e6252aaa51..0c8b9bbcf5 100644 --- a/source/java/org/alfresco/util/schemacomp/validator/NameValidator.java +++ b/source/java/org/alfresco/util/schemacomp/validator/NameValidator.java @@ -22,10 +22,13 @@ import java.util.Set; import java.util.TreeSet; import java.util.regex.Pattern; +import org.alfresco.util.ParameterCheck; import org.alfresco.util.schemacomp.DbProperty; import org.alfresco.util.schemacomp.DiffContext; import org.alfresco.util.schemacomp.ValidationResult; import org.alfresco.util.schemacomp.model.DbObject; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.hibernate.dialect.Dialect; import org.springframework.extensions.surf.util.I18NUtil; @@ -40,18 +43,34 @@ import org.springframework.extensions.surf.util.I18NUtil; public class NameValidator implements DbValidator { private Pattern pattern; + private final static Log log = LogFactory.getLog(NameValidator.class); @Override public void validate(DbObject reference, DbObject target, DiffContext ctx) { String name = target.getName(); - + + if (log.isDebugEnabled()) + { + log.debug("Validating: pattern: [" + pattern + "], reference: " + reference + ", target: " + target); + } if (pattern != null && !pattern.matcher(name).matches()) { + if (log.isDebugEnabled()) + { + log.debug("Pattern [" + pattern + "] not matched."); + } String message = I18NUtil.getMessage("system.schema_comp.name_validator", pattern); ValidationResult result = new ValidationResult(new DbProperty(target, "name"), message); ctx.getComparisonResults().add(result); } + else + { + if (log.isDebugEnabled()) + { + log.debug("Pattern [" + pattern + "] matched OK."); + } + } } @@ -96,4 +115,19 @@ public class NameValidator implements DbValidator props.add("pattern"); return props; } + + + @Override + public boolean validates(String fieldName) + { + ParameterCheck.mandatoryString("fieldName", fieldName); + return (fieldName.equals("name")); + } + + + @Override + public boolean validatesFullObject() + { + return false; + } } diff --git a/source/java/org/alfresco/util/schemacomp/validator/SchemaVersionValidator.java b/source/java/org/alfresco/util/schemacomp/validator/SchemaVersionValidator.java new file mode 100644 index 0000000000..ed50186396 --- /dev/null +++ b/source/java/org/alfresco/util/schemacomp/validator/SchemaVersionValidator.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2005-2012 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.util.schemacomp.validator; + +import org.alfresco.util.schemacomp.DbProperty; +import org.alfresco.util.schemacomp.DiffContext; +import org.alfresco.util.schemacomp.ValidationResult; +import org.alfresco.util.schemacomp.model.DbObject; +import org.alfresco.util.schemacomp.model.Schema; +import org.springframework.extensions.surf.util.I18NUtil; + +/** + * Validate a schema's version number with respect to a reference schema. + *

+ * For the target schema to pass validation its version number must be + * greater than or equal to the reference schema's version number. + * + * @author Matt Ward + */ +public class SchemaVersionValidator extends AbstractDbValidator +{ + public SchemaVersionValidator() + { + addFieldToValidate("version"); + } + + @Override + public void validate(DbObject referenceObj, DbObject targetObj, DiffContext ctx) + { + Schema reference = (Schema) referenceObj; + Schema target = (Schema) targetObj; + + if (target.getVersion() < reference.getVersion()) + { + DbProperty targetProperty = new DbProperty(target, "version"); + String message = I18NUtil.getMessage( + "system.schema_comp.schema_version_validator", + reference.getVersion()); + ctx.getComparisonResults().add(new ValidationResult(targetProperty, message)); + } + } +} diff --git a/source/java/org/alfresco/util/schemacomp/validator/SchemaVersionValidatorTest.java b/source/java/org/alfresco/util/schemacomp/validator/SchemaVersionValidatorTest.java new file mode 100644 index 0000000000..156e296ee1 --- /dev/null +++ b/source/java/org/alfresco/util/schemacomp/validator/SchemaVersionValidatorTest.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2005-2012 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.util.schemacomp.validator; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; + +import org.alfresco.util.schemacomp.DiffContext; +import org.alfresco.util.schemacomp.Results; +import org.alfresco.util.schemacomp.ValidationResult; +import org.alfresco.util.schemacomp.model.DbObject; +import org.alfresco.util.schemacomp.model.Schema; +import org.junit.Before; +import org.junit.Test; + +/** + * Tests for the SchemaVersionValidator class. + * + * @author Matt Ward + */ +public class SchemaVersionValidatorTest +{ + private SchemaVersionValidator validator; + private DbObject reference; + private DbObject target; + private DiffContext ctx; + private Results results; + + @Before + public void setUp() + { + validator = new SchemaVersionValidator(); + results = new Results(); + ctx = new DiffContext(null, results, null, null); + } + + + @Test + public void validateWhenTargetVersionPredatesReference() + { + reference = schemaWithVersion(501); + target = schemaWithVersion(500); + + validator.validate(reference, target, ctx); + + assertEquals(1, results.size()); + ValidationResult result = (ValidationResult) results.get(0); + assertEquals(500, result.getValue()); + assertEquals("version", result.getDbProperty().getPropertyName()); + assertSame(target, result.getDbProperty().getDbObject()); + } + + + @Test + public void validateWhenTargetVersionSameAsReference() + { + reference = schemaWithVersion(501); + target = schemaWithVersion(501); + + validator.validate(reference, target, ctx); + + assertEquals(0, results.size()); + } + + + @Test + public void validateWhenTargetVersionAfterReference() + { + reference = schemaWithVersion(501); + target = schemaWithVersion(502); + + validator.validate(reference, target, ctx); + + assertEquals(0, results.size()); + } + + + @Test + public void testValidates() + { + assertEquals(true, validator.validates("version")); + } + + + @Test + public void testValidatesFullObject() + { + assertEquals(false, validator.validatesFullObject()); + } + + + private DbObject schemaWithVersion(int version) + { + return new Schema("", "", version); + } +} diff --git a/source/java/org/alfresco/wcm/sandbox/SandboxFactory.java b/source/java/org/alfresco/wcm/sandbox/SandboxFactory.java index 7d93bc3d96..1b98854c13 100644 --- a/source/java/org/alfresco/wcm/sandbox/SandboxFactory.java +++ b/source/java/org/alfresco/wcm/sandbox/SandboxFactory.java @@ -567,13 +567,11 @@ public final class SandboxFactory extends WCMUtil * Website Name: .website.name = website name * * @param storeId The store id to create the sandbox for - * @param managers The list of authorities who have ContentManager role in the website * @param username Username of the user to create the sandbox for * @param role Role permission for the user * @return Summary information regarding the sandbox */ public SandboxInfo createUserSandbox(String storeId, - List managers, String username, String role) { @@ -637,11 +635,6 @@ public final class SandboxFactory extends WCMUtil permissionService.setPermission(dirRef.getStoreRef(), username, PermissionService.ALL_PERMISSIONS, true); permissionService.setPermission(dirRef.getStoreRef(), PermissionService.ALL_AUTHORITIES, PermissionService.READ, true); - // apply the manager role permission for each manager in the web project - // for (String manager : managers) - // { - // permissionService.setPermission(dirRef.getStoreRef(), manager, AVMUtil.ROLE_CONTENT_MANAGER, true); - // } // snapshot the store avmService.createSnapshot(userStoreName, null, null); @@ -680,14 +673,9 @@ public final class SandboxFactory extends WCMUtil // Apply access mask to the store (ACls are applied to the staging area) // apply the user role permissions to the sandbox - permissionService.setPermission(dirRef.getStoreRef(), currentUser, WCMUtil.ROLE_CONTENT_MANAGER, true); + permissionService.setPermission(dirRef.getStoreRef(), cms, PermissionService.WCM_CONTENT_MANAGER, true); permissionService.setPermission(dirRef.getStoreRef(), username, PermissionService.ALL_PERMISSIONS, true); permissionService.setPermission(dirRef.getStoreRef(), PermissionService.ALL_AUTHORITIES, PermissionService.READ, true); - // apply the manager role permission for each manager in the web project - for (String manager : managers) - { - permissionService.setPermission(dirRef.getStoreRef(), manager, WCMUtil.ROLE_CONTENT_MANAGER, true); - } // snapshot the store avmService.createSnapshot(previewStoreName, null, null); diff --git a/source/java/org/alfresco/wcm/sandbox/SandboxServiceImpl.java b/source/java/org/alfresco/wcm/sandbox/SandboxServiceImpl.java index 16ebe1d8d6..1c5f74fc2b 100644 --- a/source/java/org/alfresco/wcm/sandbox/SandboxServiceImpl.java +++ b/source/java/org/alfresco/wcm/sandbox/SandboxServiceImpl.java @@ -193,30 +193,9 @@ public class SandboxServiceImpl implements SandboxService WebProjectInfo wpInfo = wpService.getWebProject(wpStoreId); final NodeRef wpNodeRef = wpInfo.getNodeRef(); - final List managers = new ArrayList(4); - - AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() - { - public Object doWork() throws Exception - { - // retrieve the list of managers from the existing users - Map existingUserRoles = wpService.listWebUsers(wpNodeRef); - for (Map.Entry userRole : existingUserRoles.entrySet()) - { - String username = userRole.getKey(); - String userrole = userRole.getValue(); - - if (WCMUtil.ROLE_CONTENT_MANAGER.equals(userrole) && managers.contains(username) == false) - { - managers.add(username); - } - } - return null; - } - }, AuthenticationUtil.getSystemUserName()); String role = wpService.getWebUserRole(wpNodeRef, userName); - SandboxInfo sbInfo = sandboxFactory.createUserSandbox(wpStoreId, managers, userName, role); + SandboxInfo sbInfo = sandboxFactory.createUserSandbox(wpStoreId, userName, role); List sandboxInfoList = new LinkedList(); sandboxInfoList.add(sbInfo); diff --git a/source/java/org/alfresco/wcm/webproject/WebProjectServiceImpl.java b/source/java/org/alfresco/wcm/webproject/WebProjectServiceImpl.java index e329f343c5..39668d73ce 100644 --- a/source/java/org/alfresco/wcm/webproject/WebProjectServiceImpl.java +++ b/source/java/org/alfresco/wcm/webproject/WebProjectServiceImpl.java @@ -1,1616 +1,1616 @@ -/* - * 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.wcm.webproject; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.mbeans.VirtServerRegistry; -import org.alfresco.model.ApplicationModel; -import org.alfresco.model.ContentModel; -import org.alfresco.model.WCMAppModel; -import org.alfresco.repo.avm.AVMNodeConverter; -import org.alfresco.repo.avm.util.AVMUtil; -import org.alfresco.repo.domain.PropertyValue; -import org.alfresco.repo.rule.RuleModel; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; -import org.alfresco.repo.security.permissions.AccessDeniedException; -import org.alfresco.repo.transaction.AlfrescoTransactionSupport; -import org.alfresco.repo.transaction.TransactionListenerAdapter; -import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; -import org.alfresco.service.cmr.avm.AVMNodeDescriptor; -import org.alfresco.service.cmr.avm.AVMNotFoundException; -import org.alfresco.service.cmr.avm.AVMService; -import org.alfresco.service.cmr.avm.locking.AVMLockingService; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -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.NodeService; -import org.alfresco.service.cmr.repository.StoreRef; -import org.alfresco.service.cmr.search.ResultSet; -import org.alfresco.service.cmr.search.SearchService; -import org.alfresco.service.cmr.security.AccessStatus; -import org.alfresco.service.cmr.security.AuthorityService; -import org.alfresco.service.cmr.security.AuthorityType; -import org.alfresco.service.cmr.security.PermissionService; -import org.alfresco.service.cmr.security.PersonService; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; -import org.alfresco.service.namespace.RegexQNamePattern; -import org.alfresco.service.transaction.TransactionService; -import org.alfresco.util.DNSNameMangler; -import org.alfresco.wcm.preview.PreviewURIServiceRegistry; -import org.alfresco.wcm.sandbox.SandboxConstants; -import org.alfresco.wcm.sandbox.SandboxFactory; -import org.alfresco.wcm.sandbox.SandboxInfo; -import org.alfresco.wcm.sandbox.SandboxFactory.UserRoleWrapper; -import org.alfresco.wcm.util.WCMUtil; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.extensions.surf.util.ParameterCheck; - -/** - * Web Project Service Implementation - * - * @author janv - */ -public class WebProjectServiceImpl extends WCMUtil implements WebProjectService -{ - /** Logger */ - private static Log logger = LogFactory.getLog(WebProjectServiceImpl.class); - - /** The DM store where web projects are kept */ - public static final StoreRef WEBPROJECT_STORE = new StoreRef("workspace://SpacesStore"); - - /** The web projects root node reference */ - private NodeRef webProjectsRootNodeRef; // note: WCM is not currently MT-enabled (so this is OK) - private boolean isSetWebProjectsRootNodeRef; - - /** Services */ - private NodeService nodeService; - private NamespaceService namespaceService; - private SearchService searchService; - private AVMService avmService; - private AuthorityService authorityService; - private PermissionService permissionService; - private PersonService personService; - private SandboxFactory sandboxFactory; - private VirtServerRegistry virtServerRegistry; - private PreviewURIServiceRegistry previewURIProviderRegistry; - private TransactionService transactionService; - private AVMLockingService avmLockingService; - - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - public void setNamespaceService(NamespaceService namespaceService) - { - this.namespaceService = namespaceService; - } - - public void setSearchService(SearchService searchService) - { - this.searchService = searchService; - } - - public void setAvmService(AVMService avmService) - { - this.avmService = avmService; - } - - public void setAuthorityService(AuthorityService authorityService) - { - this.authorityService = authorityService; - } - - public void setPermissionService(PermissionService permissionService) - { - this.permissionService = permissionService; - } - - public void setPersonService(PersonService personService) - { - this.personService = personService; - } - - public void setSandboxFactory(SandboxFactory sandboxFactory) - { - this.sandboxFactory = sandboxFactory; - } - - public void setVirtServerRegistry(VirtServerRegistry virtServerRegistry) - { - this.virtServerRegistry = virtServerRegistry; - } - - public void setPreviewURIServiceRegistry(PreviewURIServiceRegistry previewURIProviderRegistry) - { - this.previewURIProviderRegistry = previewURIProviderRegistry; - } - - public void setTransactionService(TransactionService transactionService) - { - this.transactionService = transactionService; - } - - public void setAvmLockingService(AVMLockingService avmLockingService) - { - this.avmLockingService = avmLockingService; - } - - /* (non-Javadoc) - * @see org.alfresco.wcm.WebProjectService#createWebProject(java.lang.String, java.lang.String, java.lang.String, java.lang.String) - */ - public WebProjectInfo createWebProject(String dnsName, String name, String title, String description) - { - return createWebProject(dnsName, name, title, description, null, false, null); - } - - /* (non-Javadoc) - * @see org.alfresco.wcm.WebProjectService#createWebProject(java.lang.String, java.lang.String, java.lang.String, java.lang.String, org.alfresco.service.cmr.repository.NodeRef) - */ - public WebProjectInfo createWebProject(String dnsName, String name, String title, String description, NodeRef sourceNodeRef) - { - return createWebProject(dnsName, name, title, description, null, false, sourceNodeRef); - } - - /* (non-Javadoc) - * @see org.alfresco.wcm.WebProjectService#createWebProject(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, boolean, org.alfresco.service.cmr.repository.NodeRef) - */ - public WebProjectInfo createWebProject(String dnsName, String name, String title, String description, String defaultWebApp, boolean useAsTemplate, NodeRef sourceNodeRef) - { - return createWebProject(new WebProjectInfoImpl(dnsName, name, title, description, defaultWebApp, useAsTemplate, sourceNodeRef, null)); - } - - public WebProjectInfo createWebProject(WebProjectInfo wpInfo) - { - long start = System.currentTimeMillis(); - - String wpStoreId = wpInfo.getStoreId(); - String name = wpInfo.getName(); - String title = wpInfo.getTitle(); - String description = wpInfo.getDescription(); - boolean useAsTemplate = wpInfo.isTemplate(); - NodeRef sourceNodeRef = wpInfo.getNodeRef(); - String defaultWebApp = wpInfo.getDefaultWebApp(); - String previewProviderName = wpInfo.getPreviewProviderName(); - - ParameterCheck.mandatoryString("wpStoreId", wpStoreId); - ParameterCheck.mandatoryString("name", name); - - // Generate web project store id (an AVM store name) - wpStoreId = DNSNameMangler.MakeDNSName(wpStoreId); - - if (wpStoreId.indexOf(WCMUtil.STORE_SEPARATOR) != -1) - { - throw new IllegalArgumentException("Unexpected store id '"+wpStoreId+"' - should not contain '"+WCMUtil.STORE_SEPARATOR+"'"); - } - - if (wpStoreId.indexOf(AVMUtil.AVM_STORE_SEPARATOR_CHAR) != -1) - { - throw new IllegalArgumentException("Unexpected store id '"+wpStoreId+"' - should not contain '"+AVMUtil.AVM_STORE_SEPARATOR_CHAR+"'"); - } - - if (previewProviderName == null) - { - // default preview URI service provider - previewProviderName = previewURIProviderRegistry.getDefaultProviderName(); - } - else if (! previewURIProviderRegistry.getPreviewURIServiceProviders().keySet().contains(previewProviderName)) - { - throw new AlfrescoRuntimeException("Cannot update web project '" + wpInfo.getStoreId() + "' - unknown preview URI service provider ("+previewProviderName+")"); - } - - // default webapp name - defaultWebApp = (defaultWebApp != null && defaultWebApp.length() != 0) ? defaultWebApp : WCMUtil.DIR_ROOT; - - // create the website space in the correct parent folder - Map props = new HashMap(1); - props.put(ContentModel.PROP_NAME, name); - props.put(WCMAppModel.PROP_ISSOURCE, useAsTemplate); - props.put(WCMAppModel.PROP_DEFAULTWEBAPP, defaultWebApp); - props.put(WCMAppModel.PROP_AVMSTORE, wpStoreId); // reference to the root AVM store - props.put(WCMAppModel.PROP_PREVIEW_PROVIDER, previewProviderName); - - NodeRef webProjectsRoot = getWebProjectsRoot(); - - // ALF-906: ensure that DM rules are not inherited by web projects - if(!nodeService.hasAspect(webProjectsRoot, RuleModel.ASPECT_IGNORE_INHERITED_RULES)) - { - nodeService.addAspect(webProjectsRoot, RuleModel.ASPECT_IGNORE_INHERITED_RULES, null); - } - - ChildAssociationRef childAssocRef = nodeService.createNode( - webProjectsRoot, - ContentModel.ASSOC_CONTAINS, - QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, QName.createValidLocalName(name)), - WCMAppModel.TYPE_AVMWEBFOLDER, - props); - - NodeRef wpNodeRef = childAssocRef.getChildRef(); - - - // apply the uifacets aspect - icon, title and description props - Map uiFacetsProps = new HashMap(4); - uiFacetsProps.put(ApplicationModel.PROP_ICON, WCMUtil.SPACE_ICON_WEBSITE); - uiFacetsProps.put(ContentModel.PROP_TITLE, title); - uiFacetsProps.put(ContentModel.PROP_DESCRIPTION, description); - nodeService.addAspect(wpNodeRef, ApplicationModel.ASPECT_UIFACETS, uiFacetsProps); - - // branch from source web project, if supplied - String branchStoreId = null; - if (sourceNodeRef != null) - { - branchStoreId = (String)nodeService.getProperty(sourceNodeRef, WCMAppModel.PROP_AVMSTORE); - } - - // create the AVM staging store to represent the newly created location website - sandboxFactory.createStagingSandbox(wpStoreId, wpNodeRef, branchStoreId); // ignore return, fails if web project already exists - - String stagingStore = WCMUtil.buildStagingStoreName(wpStoreId); - - // create the default webapp folder under the hidden system folders - if (branchStoreId == null) - { - String stagingStoreRoot = WCMUtil.buildSandboxRootPath(stagingStore); - avmService.createDirectory(stagingStoreRoot, defaultWebApp); - avmService.addAspect(AVMNodeConverter.ExtendAVMPath(stagingStoreRoot, defaultWebApp), WCMAppModel.ASPECT_WEBAPP); - } - - // now the sandbox is created set the permissions masks for the store - sandboxFactory.setStagingPermissionMasks(wpStoreId); - - // set preview provider on staging store (used for preview lookup) - avmService.setStoreProperty(stagingStore, - SandboxConstants.PROP_WEB_PROJECT_PREVIEW_PROVIDER, - new PropertyValue(DataTypeDefinition.TEXT, previewProviderName)); - - // Snapshot the store with the empty webapp - avmService.createSnapshot(wpStoreId, null, null); - - // break the permissions inheritance on the web project node so that only assigned users can access it - permissionService.setInheritParentPermissions(wpNodeRef, false); - - // TODO: Currently auto-creates author sandbox for creator of web project (eg. an admin or a DM contributor to web projects root space) - // NOTE: JSF client does not yet allow explicit creation of author sandboxes - inviteWebUser(wpNodeRef, AuthenticationUtil.getFullyAuthenticatedUser(), WCMUtil.ROLE_CONTENT_MANAGER, true); - - // Bind the post-commit transaction listener with data required for virtualization server notification - CreateWebAppTransactionListener tl = new CreateWebAppTransactionListener(wpStoreId, WCMUtil.DIR_ROOT); - AlfrescoTransactionSupport.bindListener(tl); - - if (logger.isDebugEnabled()) - { - logger.debug("Created web project: " + wpNodeRef + " in "+(System.currentTimeMillis()-start)+" ms (store id: " + wpStoreId + ")"); - } - - // Return created web project info - return new WebProjectInfoImpl(wpStoreId, name, title, description, defaultWebApp, useAsTemplate, wpNodeRef, previewProviderName); - } - - /* (non-Javadoc) - * @see org.alfresco.wcm.webproject.WebProjectService#createWebApp(java.lang.String, java.lang.String, java.lang.String) - */ - public void createWebApp(String wpStoreId, String webAppName, String webAppDescription) - { - createWebApp(getWebProjectNodeFromStore(wpStoreId), webAppName, webAppDescription); - } - - /* (non-Javadoc) - * @see org.alfresco.wcm.webproject.WebProjectService#createWebApp(org.alfresco.service.cmr.repository.NodeRef, java.lang.String, java.lang.String) - */ - public void createWebApp(NodeRef wpNodeRef, final String webAppName, final String webAppDescription) - { - long start = System.currentTimeMillis(); - - WebProjectInfo wpInfo = getWebProject(wpNodeRef); - - if (isContentManager(wpNodeRef)) - { - // get AVM store name of the staging sandbox - final String stagingStoreId = wpInfo.getStagingStoreName(); - - AuthenticationUtil.runAs(new RunAsWork() - { - public Object doWork() throws Exception - { - final String parent = WCMUtil.buildSandboxRootPath(stagingStoreId); - avmService.createDirectory(parent, webAppName); - - String path = AVMNodeConverter.ExtendAVMPath(parent, webAppName); - avmService.addAspect(path, ApplicationModel.ASPECT_UIFACETS); - avmService.addAspect(path, WCMAppModel.ASPECT_WEBAPP); - - if (webAppDescription != null && webAppDescription.length() != 0) - { - avmService.setNodeProperty(path, - ContentModel.PROP_DESCRIPTION, - new PropertyValue(DataTypeDefinition.TEXT, - webAppDescription)); - } - - // Snapshot the store with the empty webapp - avmService.createSnapshot(stagingStoreId, null, null); - - return null; - } - }, AuthenticationUtil.getSystemUserName()); - - CreateWebAppTransactionListener tl = new CreateWebAppTransactionListener(wpInfo.getStoreId(), webAppName); - AlfrescoTransactionSupport.bindListener(tl); - - if (logger.isDebugEnabled()) - { - logger.debug("Created web app: "+webAppName+" in "+(System.currentTimeMillis()-start)+" ms (store id: "+wpInfo.getStoreId()+")"); - } - } - else - { - throw new AccessDeniedException("Only content managers may create new webapp '"+webAppName+"' (store id: "+wpInfo.getStoreId()+")"); - } - } - - /* (non-Javadoc) - * @see org.alfresco.wcm.webproject.WebProjectService#listWebApps(java.lang.String) - */ - public List listWebApps(String wpStoreId) - { - return listWebApps(getWebProjectNodeFromStore(wpStoreId)); - } - - /* (non-Javadoc) - * @see org.alfresco.wcm.webproject.WebProjectService#listWebApps(org.alfresco.service.cmr.repository.NodeRef) - */ - public List listWebApps(NodeRef wpNodeRef) - { - WebProjectInfo wpInfo = getWebProject(wpNodeRef); - - String path = WCMUtil.buildSandboxRootPath(wpInfo.getStagingStoreName()); - Map folders = avmService.getDirectoryListing(-1, path); - - List webAppNames = new ArrayList(folders.size()); - webAppNames.addAll(folders.keySet()); - return webAppNames; - } - - /* (non-Javadoc) - * @see org.alfresco.wcm.webproject.WebProjectService#deleteWebApp(java.lang.String, java.lang.String) - */ - public void deleteWebApp(String wpStoreId, String webAppName) - { - deleteWebApp(getWebProjectNodeFromStore(wpStoreId), webAppName); - } - - /* (non-Javadoc) - * @see org.alfresco.wcm.webproject.WebProjectService#deleteWebApp(org.alfresco.service.cmr.repository.NodeRef, java.lang.String) - */ - public void deleteWebApp(NodeRef wpNodeRef, final String webAppName) - { - long start = System.currentTimeMillis(); - - ParameterCheck.mandatoryString("webAppName", webAppName); - - WebProjectInfo wpInfo = getWebProject(wpNodeRef); - - if (webAppName.equals(wpInfo.getDefaultWebApp())) - { - throw new AlfrescoRuntimeException("Cannot delete default webapp '"+webAppName+"' (store id: "+wpInfo.getStoreId()+")"); - } - else if (isContentManager(wpInfo.getNodeRef())) - { - // get AVM store name of the staging sandbox - final String wpStoreId = wpInfo.getStoreId(); - - WCMUtil.removeVServerWebapp(virtServerRegistry, WCMUtil.buildStoreWebappPath(wpStoreId, webAppName), true); - - AuthenticationUtil.runAs(new RunAsWork() - { - public Object doWork() throws Exception - { - final String parent = WCMUtil.buildSandboxRootPath(wpStoreId); - - avmService.removeNode(parent, webAppName); - - // Snapshot the store with the webapp removed - avmService.createSnapshot(wpStoreId, null, null); - - return null; - } - }, AuthenticationUtil.getSystemUserName()); - - if (logger.isDebugEnabled()) - { - logger.debug("Deleted web app: "+webAppName+" in "+(System.currentTimeMillis()-start)+" ms (store id: "+wpStoreId+")"); - } - } - else - { - throw new AccessDeniedException("Only content managers may delete webapp '"+webAppName+"' (web project: "+wpNodeRef+")"); - } - } - - /* - * @see org.alfresco.wcm.webproject.WebProjectService#hasWebProjectsRoot() - */ - public boolean hasWebProjectsRoot() - { - return getWebProjectsRootOrNull() != null; - } - - private NodeRef getWebProjectsRootOrNull() - { - if (!this.isSetWebProjectsRootNodeRef) - { - // Get the root 'web projects' folder - List results = this.searchService.selectNodes(this.nodeService.getRootNode(WEBPROJECT_STORE), - getWebProjectsPath(), null, this.namespaceService, false); - int size = results.size(); - if (size > 1) - { - // More than one root web projects folder exits - throw new AlfrescoRuntimeException("More than one root 'Web Projects' folder exists"); - } - if (size > 0) - { - this.webProjectsRootNodeRef = results.get(0); - } - this.isSetWebProjectsRootNodeRef = true; - } - - return this.webProjectsRootNodeRef; - } - - /** - * Get the node reference that is the web projects root - * - * @return NodeRef node reference - */ - public NodeRef getWebProjectsRoot() - { - NodeRef result = getWebProjectsRootOrNull(); - if (result == null) - { - // No root web projects folder exists - throw new AlfrescoRuntimeException("No root 'Web Projects' folder exists (is WCM enabled ?)"); - } - return result; - } - - /* (non-Javadoc) - * @see org.alfresco.wcm.webproject.WebProjectService#listWebProjects() - */ - public List listWebProjects() - { - NodeRef wpRoot = getWebProjectsRoot(); - - Set nodeTypeQNames = new HashSet(1); - nodeTypeQNames.add(WCMAppModel.TYPE_AVMWEBFOLDER); - List webProjects = nodeService.getChildAssocs(wpRoot, nodeTypeQNames); - - List result = new ArrayList(webProjects.size()); - for (ChildAssociationRef childAssocRefs : webProjects) - { - result.add(getWebProject(childAssocRefs.getChildRef())); - } - - return result; - } - - /* (non-Javadoc) - * @see org.alfresco.wcm.webproject.WebProjectService#listWebProjects(java.lang.String) - */ - public List listWebProjects(String userName) - { - List webProjects = listWebProjects(); - List result = new ArrayList(webProjects.size()); - for (WebProjectInfo webProject : webProjects) - { - if (isWebUser(webProject.getNodeRef(), userName) == true) - { - result.add(webProject); - } - } - return result; - } - - /* (non-Javadoc) - * @see org.alfresco.wcm.webproject.WebProjectService#isWebProject(java.lang.String) - */ - public boolean isWebProject(String wpStoreId) - { - NodeRef wpNodeRef = getWebProjectNodeFromStore(wpStoreId); - if (wpNodeRef == null) - { - return false; - } - return isWebProject(wpNodeRef); - } - - /* (non-Javadoc) - * @see org.alfresco.wcm.webproject.WebProjectService#isWebProject(org.alfresco.service.cmr.repository.NodeRef) - */ - public boolean isWebProject(NodeRef wpNodeRef) - { - if (wpNodeRef == null) - { - return false; - } - - try - { - return (WCMAppModel.TYPE_AVMWEBFOLDER.equals(nodeService.getType(wpNodeRef))); - } - catch (InvalidNodeRefException e) - { - return false; - } - } - - /* (non-Javadoc) - * @see org.alfresco.wcm.webproject.WebProjectService#getWebProject(java.lang.String) - */ - public WebProjectInfo getWebProject(String wpStoreId) - { - WebProjectInfo result = null; - - // Get the web project node - NodeRef wpNodeRef = getWebProjectNodeFromStore(wpStoreId); - if (wpNodeRef != null) - { - // Create the web project info - result = getWebProject(wpNodeRef); - } - - // Return the web project info - return result; - } - - /* (non-Javadoc) - * @see org.alfresco.wcm.webproject.WebProjectService#getPreviewProvider(java.lang.String) - */ - public String getPreviewProvider(String wpStoreId) - { - ParameterCheck.mandatoryString("wpStoreId", wpStoreId); - - String previewProviderName = null; - - try - { - String stagingStoreId = WCMUtil.buildStagingStoreName(wpStoreId); - PropertyValue pValue = avmService.getStoreProperty(stagingStoreId, SandboxConstants.PROP_WEB_PROJECT_PREVIEW_PROVIDER); - - if (pValue != null) - { - previewProviderName = (String)pValue.getValue(DataTypeDefinition.TEXT); - } - } - catch (AVMNotFoundException nfe) - { - logger.warn(wpStoreId + " is not a web project: " + nfe); - } - - if (previewProviderName == null) - { - previewProviderName = previewURIProviderRegistry.getDefaultProviderName(); - } - - return previewProviderName; - } - - /* (non-Javadoc) - * @see org.alfresco.wcm.webproject.WebProjectService#getWebProject(org.alfresco.service.cmr.repository.NodeRef) - */ - public WebProjectInfo getWebProject(NodeRef wpNodeRef) - { - if (! isWebProject(wpNodeRef)) - { - throw new IllegalArgumentException(wpNodeRef + " is not a web project"); - } - - // Get the properties - Map properties = this.nodeService.getProperties(wpNodeRef); - - String name = (String)properties.get(ContentModel.PROP_NAME); - String title = (String)properties.get(ContentModel.PROP_TITLE); - String description = (String)properties.get(ContentModel.PROP_DESCRIPTION); - String wpStoreId = (String)properties.get(WCMAppModel.PROP_AVMSTORE); - String defaultWebApp = (String)properties.get(WCMAppModel.PROP_DEFAULTWEBAPP); - Boolean useAsTemplate = (Boolean)properties.get(WCMAppModel.PROP_ISSOURCE); - String previewProvider = (String)properties.get(WCMAppModel.PROP_PREVIEW_PROVIDER); - - // Create and return the web project info - WebProjectInfo wpInfo = new WebProjectInfoImpl(wpStoreId, name, title, description, defaultWebApp, useAsTemplate, wpNodeRef, previewProvider); - return wpInfo; - } - - /* (non-Javadoc) - * @see org.alfresco.wcm.webproject.WebProjectService#updateWebProject(org.alfresco.wcm.webproject.WebProjectInfo) - */ - public void updateWebProject(WebProjectInfo wpInfo) - { - long start = System.currentTimeMillis(); - - NodeRef wpNodeRef = getWebProjectNodeFromStore(wpInfo.getStoreId()); - if (wpNodeRef == null) - { - throw new AlfrescoRuntimeException("Cannot update web project '" + wpInfo.getStoreId() + "' - it does not exist."); - } - - if (! listWebApps(wpNodeRef).contains(wpInfo.getDefaultWebApp())) - { - throw new AlfrescoRuntimeException("Cannot update web project '" + wpInfo.getStoreId() + "' - unknown default web app ("+wpInfo.getDefaultWebApp()+")"); - } - - if (wpInfo.getPreviewProviderName() == null) - { - wpInfo.setPreviewProviderName(previewURIProviderRegistry.getDefaultProviderName()); - } - else if (! previewURIProviderRegistry.getPreviewURIServiceProviders().keySet().contains(wpInfo.getPreviewProviderName())) - { - throw new AlfrescoRuntimeException("Cannot update web project '" + wpInfo.getStoreId() + "' - unknown preview URI service provider ("+wpInfo.getPreviewProviderName()+")"); - } - - // Note: the site preset and short name can not be updated - - // Update the properties of the site - note: cannot change storeId or wpNodeRef - Map properties = this.nodeService.getProperties(wpNodeRef); - - properties.put(ContentModel.PROP_NAME, wpInfo.getName()); - properties.put(ContentModel.PROP_TITLE, wpInfo.getTitle()); - properties.put(ContentModel.PROP_DESCRIPTION, wpInfo.getDescription()); - properties.put(WCMAppModel.PROP_DEFAULTWEBAPP, wpInfo.getDefaultWebApp()); - properties.put(WCMAppModel.PROP_ISSOURCE, wpInfo.isTemplate()); - properties.put(WCMAppModel.PROP_PREVIEW_PROVIDER, wpInfo.getPreviewProviderName()); - - this.nodeService.setProperties(wpNodeRef, properties); - - // set preview provider on staging store (used for preview lookup) - String stagingStore = WCMUtil.buildStagingStoreName(wpInfo.getStoreId()); - - avmService.deleteStoreProperty(stagingStore, SandboxConstants.PROP_WEB_PROJECT_PREVIEW_PROVIDER); - avmService.setStoreProperty(stagingStore, - SandboxConstants.PROP_WEB_PROJECT_PREVIEW_PROVIDER, - new PropertyValue(DataTypeDefinition.TEXT, wpInfo.getPreviewProviderName())); - - if (logger.isDebugEnabled()) - { - logger.debug("Updated web project: " + wpNodeRef + " in "+(System.currentTimeMillis()-start)+" ms (store id: " + wpInfo.getStoreId() + ")"); - } - } - - /* (non-Javadoc) - * @see org.alfresco.wcm.webproject.WebProjectService#deleteWebProject(java.lang.String) - */ - public void deleteWebProject(String wpStoreId) - { - NodeRef wpNodeRef = getWebProjectNodeFromStore(wpStoreId); - if (wpNodeRef != null) - { - deleteWebProject(wpNodeRef); - } - else - { - // by definition, the current user is not a content manager since the web project does not exist (or is not visible) - throw new AccessDeniedException("Only content managers may delete a web project"); - } - } - - /* (non-Javadoc) - * @see org.alfresco.wcm.webproject.WebProjectService#deleteWebProject(org.alfresco.service.cmr.repository.NodeRef) - */ - public void deleteWebProject(final NodeRef wpNodeRef) - { - long start = System.currentTimeMillis(); - - if (! isContentManager(wpNodeRef)) - { - // the current user is not a content manager since the web project does not exist (or is not visible) - throw new AccessDeniedException("Only content managers may delete web project"); - } - - // delete all attached website sandboxes in reverse order to the layering - final String wpStoreId = (String)nodeService.getProperty(wpNodeRef, WCMAppModel.PROP_AVMSTORE); - - if (wpStoreId != null) - { - // Notify virtualization server about removing this website - // - // Implementation note: - // - // Because the removal of virtual webapps in the virtualization - // server is recursive, it only needs to be given the name of - // the main staging store. - // - // This notification must occur *prior* to purging content - // within the AVM because the virtualization server must list - // the avm_webapps dir in each store to discover which - // virtual webapps must be unloaded. The virtualization - // server traverses the sandbox's stores in most-to-least - // dependent order, so clients don't have to worry about - // accessing a preview layer whose main layer has been torn - // out from under it. - // - // It does not matter what webapp name we give here, so "/ROOT" - // is as sensible as anything else. It's all going away. - - final String sandbox = WCMUtil.buildStagingStoreName(wpStoreId); - String path = WCMUtil.buildStoreWebappPath(sandbox, "/ROOT"); - - WCMUtil.removeAllVServerWebapps(virtServerRegistry, path, true); - - try - { - RetryingTransactionCallback deleteWebProjectWork = new RetryingTransactionCallback() - { - public Object execute() throws Throwable - { - AuthenticationUtil.runAs(new RunAsWork() - { - public Object doWork() throws Exception - { - List sbInfos = sandboxFactory.listAllSandboxes(wpStoreId, true, true); - - for (SandboxInfo sbInfo : sbInfos) - { - String sbStoreId = sbInfo.getSandboxId(); - - if (WCMUtil.isLocalhostDeployedStore(wpStoreId, sbStoreId)) - { - if (getWebProject(WCMUtil.getWebProjectStoreId(sbStoreId)) != null) - { - continue; - } - } - - // delete sandbox (and associated preview sandbox, if it exists) - sandboxFactory.deleteSandbox(sbInfo, false, false); - } - - // delete all web project locks in one go (ie. all those currently held against staging store) - avmLockingService.removeLocks(wpStoreId); - - StoreRef archiveStoreRef = nodeService.getStoreArchiveNode(wpNodeRef.getStoreRef()).getStoreRef(); - - // delete the web project node itself - nodeService.deleteNode(wpNodeRef); - nodeService.deleteNode(new NodeRef(archiveStoreRef, wpNodeRef.getId())); - - sandboxFactory.removeGroupsForStore(sandbox); - - return null; - } - }, AuthenticationUtil.getSystemUserName()); - - return null; - } - }; - - transactionService.getRetryingTransactionHelper().doInTransaction(deleteWebProjectWork); - - if (logger.isDebugEnabled()) - { - logger.debug("Deleted web project: " + wpNodeRef + " in "+(System.currentTimeMillis()-start)+" ms (store id: " + wpStoreId + ")"); - } - } - catch (Throwable err) - { - throw new AlfrescoRuntimeException("Failed to delete web project: ", err); - } - } - } - - /* (non-Javadoc) - * @see org.alfresco.wcm.webproject.WebProjectService#isContentManager(java.lang.String) - */ - public boolean isContentManager(String storeName) - { - return isContentManager(storeName, AuthenticationUtil.getFullyAuthenticatedUser()); - } - - /* (non-Javadoc) - * @see org.alfresco.wcm.WebProjectService#isContentManager(java.lang.String, java.lang.String) - */ - public boolean isContentManager(String wpStoreId, String userName) - { - return isContentManager(getWebProjectNodeFromStore(wpStoreId), userName); - } - - /* (non-Javadoc) - * @see org.alfresco.wcm.webproject.WebProjectService#isContentManager(org.alfresco.service.cmr.repository.NodeRef) - */ - public boolean isContentManager(NodeRef wpNodeRef) - { - return isContentManager(wpNodeRef, AuthenticationUtil.getFullyAuthenticatedUser()); - } - - /* (non-Javadoc) - * @see org.alfresco.wcm.webproject.WebProjectService#isContentManager(org.alfresco.service.cmr.repository.NodeRef, java.lang.String) - */ - public boolean isContentManager(NodeRef wpNodeRef, String userName) - { - String userRole = getWebUserRole(wpNodeRef, userName); - return WCMUtil.ROLE_CONTENT_MANAGER.equals(userRole); - } - - /* (non-Javadoc) - * @see org.alfresco.wcm.webproject.WebProjectService#isWebUser(java.lang.String) - */ - public boolean isWebUser(String wpStoreId) - { - return isWebUser(getWebProjectNodeFromStore(wpStoreId)); - } - - /* (non-Javadoc) - * @see org.alfresco.wcm.webproject.WebProjectService#isWebUser(org.alfresco.service.cmr.repository.NodeRef) - */ - public boolean isWebUser(NodeRef wpNodeRef) - { - // note: admin is an implied web user (content manager) although will not appear in listWebUsers unless explicitly invited - return (permissionService.hasPermission(wpNodeRef, PermissionService.READ) == AccessStatus.ALLOWED); - } - - /* (non-Javadoc) - * @see org.alfresco.wcm.webproject.WebProjectService#isWebUser(java.lang.String, java.lang.String) - */ - public boolean isWebUser(String wpStoreId, String username) - { - return isWebUser(getWebProjectNodeFromStore(wpStoreId), username); - } - - /* (non-Javadoc) - * @see org.alfresco.wcm.webproject.WebProjectService#isWebUser(org.alfresco.service.cmr.repository.NodeRef, java.lang.String) - */ - public boolean isWebUser(final NodeRef wpNodeRef, String userName) - { - return AuthenticationUtil.runAs(new RunAsWork() - { - public Boolean doWork() throws Exception - { - return isWebUser(wpNodeRef); - } - }, userName); - } - - /* (non-Javadoc) - * @see org.alfresco.wcm.webproject.WebProjectService#getWebUserCount(org.alfresco.service.cmr.repository.NodeRef) - */ - public int getWebUserCount(NodeRef wpNodeRef) - { - long start = System.currentTimeMillis(); - - int cnt = WCMUtil.listWebUserRefs(nodeService, wpNodeRef, false).size(); - - if (logger.isTraceEnabled()) - { - logger.trace("Get web user cnt: " + wpNodeRef + "(" + cnt + ") in "+(System.currentTimeMillis()-start)+" ms"); - } - - return cnt; - } - - /* (non-Javadoc) - * @see org.alfresco.wcm.webproject.WebProjectService#listWebUsers(java.lang.String) - */ - public Map listWebUsers(String wpStoreId) - { - return listWebUsers(getWebProjectNodeFromStore(wpStoreId)); - } - - /* (non-Javadoc) - * @see org.alfresco.wcm.webproject.WebProjectService#listWebUsers(org.alfresco.service.cmr.repository.NodeRef) - */ - public Map listWebUsers(NodeRef wpNodeRef) - { - long start = System.currentTimeMillis(); - - // special case: allow System - eg. to allow user to create their own sandbox on-demand (createAuthorSandbox) - if (isContentManager(wpNodeRef) - || (AuthenticationUtil.getRunAsUser().equals(AuthenticationUtil.getSystemUserName()) - || (permissionService.hasPermission(wpNodeRef, PermissionService.ADD_CHILDREN) == AccessStatus.ALLOWED))) - { - Map users = WCMUtil.listWebUsers(nodeService, wpNodeRef); - - if (logger.isTraceEnabled()) - { - logger.trace("List web users: " + wpNodeRef + "(" + users.size() + ") in "+(System.currentTimeMillis()-start)+" ms"); - } - - return users; - } - else - { - throw new AccessDeniedException("Only content managers may list users in a web project"); - } - } - - /* (non-Javadoc) - * @see org.alfresco.wcm.webproject.WebProjectService#getWebUserRole(java.lang.String, java.lang.String) - */ - public String getWebUserRole(String wpStoreId, String userName) - { - return getWebUserRole(getWebProjectNodeFromStore(wpStoreId), userName); - } - - /* (non-Javadoc) - * @see org.alfresco.wcm.webproject.WebProjectService#getWebUserRole(org.alfresco.service.cmr.repository.NodeRef, java.lang.String) - */ - public String getWebUserRole(NodeRef wpNodeRef, String userName) - { - ParameterCheck.mandatoryString("userName", userName); - - String userRole = null; - - if (! isWebProject(wpNodeRef)) - { - logger.warn(wpNodeRef + " is not a web project"); - return null; - } - - if (authorityService.isAdminAuthority(userName)) - { - // fake the Content Manager role for an admin user - userRole = WCMUtil.ROLE_CONTENT_MANAGER; - } - else - { - userRole = getWebUserRoleImpl(wpNodeRef, userName); - } - - return userRole; - } - - private String getWebUserRoleImpl(NodeRef wpNodeRef, String userName) - { - NodeRef userRef = getWebUserRef(wpNodeRef, userName); - String userRole = null; - - if (userRef != null) - { - userRole = (String)nodeService.getProperty(userRef, WCMAppModel.PROP_WEBUSERROLE); - } - - return userRole; - } - - private NodeRef getWebUserRef(NodeRef wpNodeRef, String userName) - { - StringBuilder query = new StringBuilder(128); - query.append("+PARENT:\"").append(wpNodeRef).append("\" "); - query.append("+TYPE:\"").append(WCMAppModel.TYPE_WEBUSER).append("\" "); - query.append("+@").append(NamespaceService.WCMAPP_MODEL_PREFIX).append("\\:username:\""); - query.append(userName); - query.append("\""); - - ResultSet resultSet = null; - List nodes = null; - try - { - resultSet = searchService.query( - WEBPROJECT_STORE, - SearchService.LANGUAGE_LUCENE, - query.toString()); - nodes = resultSet.getNodeRefs(); - } - finally - { - if (resultSet != null) - { - resultSet.close(); - } - } - - // Lucene indexing may strip certain international characters or treat them as equivalent so we do string - // comparisons on the results to ensure an exact match - Iterator i = nodes.iterator(); - while (i.hasNext()) - { - if (!nodeService.getProperty(i.next(), WCMAppModel.PROP_WEBUSERNAME).equals(userName)) - { - i.remove(); - } - } - - if (nodes.size() == 1) - { - return nodes.get(0); - } - else if (nodes.size() == 0) - { - if (logger.isTraceEnabled()) - { - logger.trace("getWebUserRef: web user ("+userName+") not found in web project: "+wpNodeRef); - } - } - else - { - logger.error("getWebUserRef: more than one web user ("+userName+") found in web project: "+wpNodeRef); - } - - return null; - } - - - /* (non-Javadoc) - * @see org.alfresco.wcm.webproject.WebProjectService#findWebProjectNodeFromPath(java.lang.String) - */ - public NodeRef getWebProjectNodeFromPath(String absoluteAVMPath) - { - return getWebProjectNodeFromStore(WCMUtil.getWebProjectStoreIdFromPath(absoluteAVMPath)); - } - - /*(non-Javadoc) - * @see org.alfresco.wcm.webproject.WebProjectService#getWebProjectNodeFromStore(java.lang.String) - */ - public NodeRef getWebProjectNodeFromStore(String wpStoreId) - { - ParameterCheck.mandatoryString("wpStoreId", wpStoreId); - - return WCMUtil.getWebProjectNodeFromWebProjectStore(avmService, wpStoreId); - } - - /* (non-Javadoc) - * @see org.alfresco.wcm.webproject.WebProjectService#inviteWebUsersGroups(java.lang.String, java.util.Map) - */ - public void inviteWebUsersGroups(String wpStoreId, Map userGroupRoles) - { - inviteWebUsersGroups(getWebProjectNodeFromStore(wpStoreId), userGroupRoles, false); - } - - /* (non-Javadoc) - * @see org.alfresco.wcm.webproject.WebProjectService#inviteWebUsersGroups(java.lang.String, java.util.Map, boolean) - */ - public void inviteWebUsersGroups(String wpStoreId, Map userGroupRoles, boolean autoCreateAuthorSandbox) - { - inviteWebUsersGroups(getWebProjectNodeFromStore(wpStoreId), userGroupRoles, autoCreateAuthorSandbox); - } - - public void inviteWebUsersGroups(NodeRef wpNodeRef, Map userGroupRoles, boolean autoCreateAuthorSandbox) - { - long start = System.currentTimeMillis(); - - if (! (isContentManager(wpNodeRef) || - permissionService.hasPermission(wpNodeRef, PermissionService.ADD_CHILDREN) == AccessStatus.ALLOWED)) - { - throw new AccessDeniedException("Only content managers may invite web users"); - } - - WebProjectInfo wpInfo = getWebProject(wpNodeRef); - String wpStoreId = wpInfo.getStoreId(); - - // build a list of managers who will have full permissions on ALL staging areas - List managers = new ArrayList(4); - Map webSiteUsers = new HashMap(8); - List managersToRemove = new LinkedList(); - List usersToUpdate = new LinkedList(); - - // retrieve the list of managers from the existing users - for (Map.Entry userRole : userGroupRoles.entrySet()) - { - String authority = userRole.getKey(); - String role = userRole.getValue(); - - for (String userAuth : findNestedUserAuthorities(authority)) - { - if (WCMUtil.ROLE_CONTENT_MANAGER.equals(role)) - { - managers.add(userAuth); - } - } - } - - List userInfoRefs = nodeService.getChildAssocs(wpNodeRef, WCMAppModel.ASSOC_WEBUSER, RegexQNamePattern.MATCH_ALL); - - for (ChildAssociationRef ref : userInfoRefs) - { - NodeRef userInfoRef = ref.getChildRef(); - String username = (String)nodeService.getProperty(userInfoRef, WCMAppModel.PROP_WEBUSERNAME); - String userrole = (String)nodeService.getProperty(userInfoRef, WCMAppModel.PROP_WEBUSERROLE); - - if (WCMUtil.ROLE_CONTENT_MANAGER.equals(userrole) && managers.contains(username) == false) - { - managers.add(username); - } - - // add each existing user to the map which will be rechecked for update changed user permissions - webSiteUsers.put(username, userInfoRef); - } - - List sandboxInfoList = new LinkedList(); - - int invitedCount = 0; - boolean managersUpdateRequired = false; - - for (Map.Entry userRole : userGroupRoles.entrySet()) - { - String authority = userRole.getKey(); - String role = userRole.getValue(); - - for (String userAuth : findNestedUserAuthorities(authority)) - { - if (webSiteUsers.keySet().contains(userAuth) == false) - { - if (autoCreateAuthorSandbox) - { - // create a sandbox for the user with permissions based on role - SandboxInfo sbInfo = sandboxFactory.createUserSandbox(wpStoreId, managers, userAuth, role); - sandboxInfoList.add(sbInfo); - } - - sandboxFactory.addStagingAreaUser(wpStoreId, userAuth, role); - - // create an app:webuser instance for each authority and assoc to the web project node - createWebUser(wpNodeRef, userAuth, role); - - // if this new user is a manager, we'll need to update the manager permissions applied - // to each existing user sandbox - to ensure that new managers have access to them - managersUpdateRequired |= (WCMUtil.ROLE_CONTENT_MANAGER.equals(role)); - - invitedCount++; - } - else - { - // TODO - split out into separate 'change role' - // if user role have been changed then update required properties etc. - NodeRef userRef = webSiteUsers.get(userAuth); - String oldUserRole = (String)nodeService.getProperty(userRef, WCMAppModel.PROP_WEBUSERROLE); - - if (!role.equals(oldUserRole)) - { - // change in role - Map props = nodeService.getProperties(userRef); - props.put(WCMAppModel.PROP_WEBUSERNAME, userAuth); - props.put(WCMAppModel.PROP_WEBUSERROLE, role); - nodeService.setProperties(userRef, props); - - if (WCMUtil.ROLE_CONTENT_MANAGER.equals(role)) - { - managersUpdateRequired = true; - } - else if (WCMUtil.ROLE_CONTENT_MANAGER.equals(oldUserRole)) - { - managersToRemove.add(userAuth); - } - - usersToUpdate.add(sandboxFactory.new UserRoleWrapper(userAuth, oldUserRole, role)); - - if (logger.isDebugEnabled()) - { - logger.debug(userAuth +"'s role has been changed from '" + oldUserRole + - "' to '" + role + "'"); - } - } - } - } - } - - // Bind the post-commit transaction listener with data required for virtualization server notification - CreateSandboxTransactionListener tl = new CreateSandboxTransactionListener(sandboxInfoList, listWebApps(wpNodeRef)); - AlfrescoTransactionSupport.bindListener(tl); - - if (managersUpdateRequired == true) - { - sandboxFactory.updateSandboxManagers(wpStoreId, managers); - } - - // TODO - split out into separate 'change role' - // remove ex-managers from sandboxes - if (managersToRemove.size() != 0) - { - sandboxFactory.removeSandboxManagers(wpStoreId, managersToRemove); - } - - // get permissions and roles for a web project folder type - Set perms = permissionService.getSettablePermissions(WCMAppModel.TYPE_AVMWEBFOLDER); - - // set permissions for each user - for (Map.Entry userRole : userGroupRoles.entrySet()) - { - String authority = userRole.getKey(); - String role = userRole.getValue(); - - for (String permission : perms) - { - if (role.equals(permission)) - { - permissionService.setPermission(wpNodeRef, - authority, - permission, - true); - break; - } - } - } - - // TODO - split out into separate 'change role' - // update user's roles - if (usersToUpdate.size() != 0) - { - sandboxFactory.updateSandboxRoles(wpStoreId, usersToUpdate, perms); - } - - if (logger.isDebugEnabled()) - { - logger.debug("Invited "+invitedCount+" web users in "+(System.currentTimeMillis()-start)+" ms (store id: "+wpStoreId+")"); - } - } - - /* (non-Javadoc) - * @see org.alfresco.wcm.webproject.WebProjectService#inviteWebUser(java.lang.String, java.lang.String, java.lang.String) - */ - public void inviteWebUser(String wpStoreId, String userAuth, String role) - { - inviteWebUser(getWebProjectNodeFromStore(wpStoreId), userAuth, role, false); - } - - /* (non-Javadoc) - * @see org.alfresco.wcm.webproject.WebProjectService#inviteWebUser(java.lang.String, java.lang.String, java.lang.String, boolean) - */ - public void inviteWebUser(String wpStoreId, String userAuth, String role, boolean autoCreateAuthorSandbox) - { - inviteWebUser(getWebProjectNodeFromStore(wpStoreId), userAuth, role, autoCreateAuthorSandbox); - } - - /* (non-Javadoc) - * @see org.alfresco.wcm.webproject.WebProjectService#inviteWebUser(org.alfresco.service.cmr.repository.NodeRef, java.lang.String, java.lang.String, boolean) - */ - public void inviteWebUser(NodeRef wpNodeRef, String userAuth, String role, boolean autoCreateAuthorSandbox) - { - long start = System.currentTimeMillis(); - - if (! (isContentManager(wpNodeRef) || - permissionService.hasPermission(wpNodeRef, PermissionService.ADD_CHILDREN) == AccessStatus.ALLOWED)) - { - throw new AccessDeniedException("Only content managers may invite web user"); - } - - WebProjectInfo wpInfo = getWebProject(wpNodeRef); - final String wpStoreId = wpInfo.getStoreId(); - - // build a list of managers who will have full permissions on ALL staging areas - List managers = new ArrayList(4); - - // retrieve the list of managers from the existing users - Map existingUserRoles = listWebUsers(wpNodeRef); - for (Map.Entry userRole : existingUserRoles.entrySet()) - { - String username = userRole.getKey(); - String userrole = userRole.getValue(); - - if (WCMUtil.ROLE_CONTENT_MANAGER.equals(userrole) && managers.contains(username) == false) - { - managers.add(username); - } - } - - // get permissions and roles for a web project folder type - Set perms = permissionService.getSettablePermissions(WCMAppModel.TYPE_AVMWEBFOLDER); - - NodeRef userRef = getWebUserRef(wpNodeRef, userAuth); - if (userRef != null) - { - // TODO - split out into separate 'change role' - // if user role has been changed then update required properties etc. - String oldUserRole = (String)nodeService.getProperty(userRef, WCMAppModel.PROP_WEBUSERROLE); - if (!role.equals(oldUserRole)) - { - // change in role - Map props = nodeService.getProperties(userRef); - props.put(WCMAppModel.PROP_WEBUSERNAME, userAuth); - props.put(WCMAppModel.PROP_WEBUSERROLE, role); - nodeService.setProperties(userRef, props); - - if (WCMUtil.ROLE_CONTENT_MANAGER.equals(role)) - { - managers.add(userAuth); - sandboxFactory.updateSandboxManagers(wpStoreId, managers); - } - else if (WCMUtil.ROLE_CONTENT_MANAGER.equals(oldUserRole)) - { - List managersToRemove = new LinkedList(); - managersToRemove.add(userAuth); - - sandboxFactory.removeSandboxManagers(wpStoreId, managersToRemove); - } - - List usersToUpdate = new LinkedList(); - usersToUpdate.add(sandboxFactory.new UserRoleWrapper(userAuth, oldUserRole, role)); - - sandboxFactory.updateSandboxRoles(wpStoreId, usersToUpdate, perms); - - if (logger.isDebugEnabled()) - { - logger.debug("Web user "+userAuth +"'s role has been changed from '" + oldUserRole + "' to '" + role + "' in "+(System.currentTimeMillis()-start)+" ms (store id: "+wpStoreId+")"); - } - } - } - else - { - if (autoCreateAuthorSandbox) - { - // create a sandbox for the user with permissions based on role - SandboxInfo sbInfo = sandboxFactory.createUserSandbox(wpStoreId, managers, userAuth, role); - - List sandboxInfoList = new LinkedList(); - sandboxInfoList.add(sbInfo); - - // Bind the post-commit transaction listener with data required for virtualization server notification - CreateSandboxTransactionListener tl = new CreateSandboxTransactionListener(sandboxInfoList, listWebApps(wpNodeRef)); - AlfrescoTransactionSupport.bindListener(tl); - } - - // if this new user is a manager, we'll need to update the manager permissions applied - // to each existing user sandbox - to ensure that new user has access to them - if (WCMUtil.ROLE_CONTENT_MANAGER.equals(role)) - { - managers.add(userAuth); - sandboxFactory.updateSandboxManagers(wpStoreId, managers); - } - - sandboxFactory.addStagingAreaUser(wpStoreId, userAuth, role); - - // create an app:webuser instance for the user and assoc to the web project node - createWebUser(wpNodeRef, userAuth, role); - - // set permissions for the user - for (String permission : perms) - { - if (role.equals(permission)) - { - permissionService.setPermission(wpNodeRef, - userAuth, - permission, - true); - break; - } - } - - if (logger.isDebugEnabled()) - { - logger.debug("Invited web user: "+userAuth+" in "+(System.currentTimeMillis()-start)+" ms (store id: "+wpStoreId+")"); - } - } - } - - private void createWebUser(NodeRef wpNodeRef, String userName, String userRole) - { - // create an app:webuser instance for the user and assoc to the web project node - Map props = new HashMap(2, 1.0f); - props.put(WCMAppModel.PROP_WEBUSERNAME, userName); - props.put(WCMAppModel.PROP_WEBUSERROLE, userRole); - nodeService.createNode(wpNodeRef, - WCMAppModel.ASSOC_WEBUSER, - WCMAppModel.ASSOC_WEBUSER, - WCMAppModel.TYPE_WEBUSER, - props); - } - - /* (non-Javadoc) - * @see org.alfresco.wcm.webproject.WebProjectService#uninviteWebUser(java.lang.String, java.lang.String) - */ - public void uninviteWebUser(String wpStoreId, String userAuth) - { - uninviteWebUser(getWebProjectNodeFromStore(wpStoreId), userAuth, false); - } - - /* (non-Javadoc) - * @see org.alfresco.wcm.webproject.WebProjectService#uninviteWebUser(java.lang.String, java.lang.String, boolean) - */ - public void uninviteWebUser(String wpStoreId, String userAuth, boolean autoDeleteAuthorSandbox) - { - uninviteWebUser(getWebProjectNodeFromStore(wpStoreId), userAuth, autoDeleteAuthorSandbox); - } - - /* (non-Javadoc) - * @see org.alfresco.wcm.webproject.WebProjectService#uninviteWebUser(org.alfresco.service.cmr.repository.NodeRef, java.lang.String, boolean) - */ - public void uninviteWebUser(NodeRef wpNodeRef, String userAuth, boolean autoDeleteAuthorSandbox) - { - long start = System.currentTimeMillis(); - - if (! isContentManager(wpNodeRef)) - { - throw new AccessDeniedException("Only content managers may uninvite web user '"+userAuth+"' from web project: "+wpNodeRef); - } - - ParameterCheck.mandatory("wpNodeRef", wpNodeRef); - ParameterCheck.mandatoryString("userAuth", userAuth); - - WebProjectInfo wpInfo = getWebProject(wpNodeRef); - String wpStoreId = wpInfo.getStoreId(); - String userMainStore = WCMUtil.buildUserMainStoreName(wpStoreId, userAuth); - - if (autoDeleteAuthorSandbox) - { - sandboxFactory.deleteSandbox(userMainStore); - } - - // remove the store reference from the website folder meta-data (see also WCMUtil.listWebUsers) - List userInfoRefs = nodeService.getChildAssocs(wpNodeRef, WCMAppModel.ASSOC_WEBUSER, RegexQNamePattern.MATCH_ALL); - - // retrieve the list of managers from the existing users - List managers = new ArrayList(4); - for (ChildAssociationRef ref : userInfoRefs) - { - NodeRef userInfoRef = ref.getChildRef(); - String username = (String)nodeService.getProperty(userInfoRef, WCMAppModel.PROP_WEBUSERNAME); - String userrole = (String)nodeService.getProperty(userInfoRef, WCMAppModel.PROP_WEBUSERROLE); - - if (WCMUtil.ROLE_CONTENT_MANAGER.equals(userrole) && managers.contains(username) == false) - { - managers.add(username); - } - } - - for (ChildAssociationRef ref : userInfoRefs) - { - NodeRef userInfoRef = ref.getChildRef(); - String user = (String)nodeService.getProperty(userInfoRef, WCMAppModel.PROP_WEBUSERNAME); - - if (userAuth.equals(user)) - { - // remove the association to this web project user meta-data - nodeService.removeChild(wpNodeRef, ref.getChildRef()); - - // remove permission for the user (also fixes ETWOONE-338) - permissionService.clearPermission(wpNodeRef, userAuth); - - if (logger.isDebugEnabled()) - { - logger.debug("Uninvited web user: "+userAuth+" in "+(System.currentTimeMillis()-start)+" ms (store id: "+wpStoreId+")"); - } - - break; // for loop - } - } - } - - /** - * Find all nested user authorities contained with an authority - * - * @param authority The authority to search, USER authorities are returned immediately, GROUP authorites - * are recursively scanned for contained USER authorities. - * - * @return a Set of USER authorities - */ - private Set findNestedUserAuthorities(String authority) - { - Set users; - - AuthorityType authType = AuthorityType.getAuthorityType(authority); - if (authType.equals(AuthorityType.USER)) - { - users = new HashSet(1, 1.0f); - if (personService.personExists(authority) == true) - { - users.add(authority); - } - } - else if (authType.equals(AuthorityType.GROUP)) - { - // walk each member of the group - users = authorityService.getContainedAuthorities(AuthorityType.USER, authority, false); - for (String userAuth : users) - { - if (personService.personExists(userAuth) == false) - { - users.remove(authType); - } - } - } - else - { - users = Collections.emptySet(); - } - - return users; - } - - private String getWebProjectsPath() - { - return "/"+SPACES_COMPANY_HOME_CHILDNAME+"/"+SPACES_WCM_CHILDNAME; - } - - private static final String SPACES_COMPANY_HOME_CHILDNAME = "app:company_home"; // should match repository property: spaces.company_home.childname - private static final String SPACES_WCM_CHILDNAME = "app:wcm"; // should match repository property: spaces.wcm.childname - - - /** - * Create WebProject/WebApp Transaction listener - invoked after commit - */ - private class CreateWebAppTransactionListener extends TransactionListenerAdapter - { - private String wpStoreId; - private String webApp; - - public CreateWebAppTransactionListener(String wpStoreId, String webApp) - { - this.wpStoreId = wpStoreId; - this.webApp = webApp; - } - - /** - * @see org.alfresco.repo.transaction.TransactionListenerAdapter#afterCommit() - */ - @Override - public void afterCommit() - { - // post-commit - if (wpStoreId != null) - { - // update the virtualisation server with webapp - // performed after the main txn has committed successfully - String newStoreName = WCMUtil.buildStagingStoreName(wpStoreId); - - String path = WCMUtil.buildStoreWebappPath(newStoreName, webApp); - - WCMUtil.updateVServerWebapp(virtServerRegistry, path, true); - } - } - } - - /** - * Create Sandbox Transaction listener - invoked after commit - */ - private class CreateSandboxTransactionListener extends TransactionListenerAdapter - { - private List sandboxInfoList; - private List webAppNames; - - public CreateSandboxTransactionListener(List sandboxInfoList, List webAppNames) - { - this.sandboxInfoList = sandboxInfoList; - this.webAppNames = webAppNames; - } - - /** - * @see org.alfresco.repo.transaction.TransactionListenerAdapter#afterCommit() - */ - @Override - public void afterCommit() - { - // Handle notification to the virtualization server - // (this needs to occur after the sandboxes are created in the main txn) - - // reload virtualisation server for webapp(s) in this web project - for (SandboxInfo sandboxInfo : this.sandboxInfoList) - { - String newlyInvitedStoreName = WCMUtil.buildStagingStoreName(sandboxInfo.getMainStoreName()); - - for (String webAppName : webAppNames) - { - String path = WCMUtil.buildStoreWebappPath(newlyInvitedStoreName, webAppName); - WCMUtil.updateVServerWebapp(virtServerRegistry, path, true); - } - } - } - } -} +/* + * 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.wcm.webproject; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.mbeans.VirtServerRegistry; +import org.alfresco.model.ApplicationModel; +import org.alfresco.model.ContentModel; +import org.alfresco.model.WCMAppModel; +import org.alfresco.repo.avm.AVMNodeConverter; +import org.alfresco.repo.avm.util.AVMUtil; +import org.alfresco.repo.domain.PropertyValue; +import org.alfresco.repo.rule.RuleModel; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.security.permissions.AccessDeniedException; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; +import org.alfresco.repo.transaction.TransactionListenerAdapter; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.cmr.avm.AVMNodeDescriptor; +import org.alfresco.service.cmr.avm.AVMNotFoundException; +import org.alfresco.service.cmr.avm.AVMService; +import org.alfresco.service.cmr.avm.locking.AVMLockingService; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +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.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.search.ResultSet; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.cmr.security.AccessStatus; +import org.alfresco.service.cmr.security.AuthorityService; +import org.alfresco.service.cmr.security.AuthorityType; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.namespace.RegexQNamePattern; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.DNSNameMangler; +import org.alfresco.wcm.preview.PreviewURIServiceRegistry; +import org.alfresco.wcm.sandbox.SandboxConstants; +import org.alfresco.wcm.sandbox.SandboxFactory; +import org.alfresco.wcm.sandbox.SandboxInfo; +import org.alfresco.wcm.sandbox.SandboxFactory.UserRoleWrapper; +import org.alfresco.wcm.util.WCMUtil; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.extensions.surf.util.ParameterCheck; + +/** + * Web Project Service Implementation + * + * @author janv + */ +public class WebProjectServiceImpl extends WCMUtil implements WebProjectService +{ + /** Logger */ + private static Log logger = LogFactory.getLog(WebProjectServiceImpl.class); + + /** The DM store where web projects are kept */ + public static final StoreRef WEBPROJECT_STORE = new StoreRef("workspace://SpacesStore"); + + /** The web projects root node reference */ + private NodeRef webProjectsRootNodeRef; // note: WCM is not currently MT-enabled (so this is OK) + private boolean isSetWebProjectsRootNodeRef; + + /** Services */ + private NodeService nodeService; + private NamespaceService namespaceService; + private SearchService searchService; + private AVMService avmService; + private AuthorityService authorityService; + private PermissionService permissionService; + private PersonService personService; + private SandboxFactory sandboxFactory; + private VirtServerRegistry virtServerRegistry; + private PreviewURIServiceRegistry previewURIProviderRegistry; + private TransactionService transactionService; + private AVMLockingService avmLockingService; + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + public void setSearchService(SearchService searchService) + { + this.searchService = searchService; + } + + public void setAvmService(AVMService avmService) + { + this.avmService = avmService; + } + + public void setAuthorityService(AuthorityService authorityService) + { + this.authorityService = authorityService; + } + + public void setPermissionService(PermissionService permissionService) + { + this.permissionService = permissionService; + } + + public void setPersonService(PersonService personService) + { + this.personService = personService; + } + + public void setSandboxFactory(SandboxFactory sandboxFactory) + { + this.sandboxFactory = sandboxFactory; + } + + public void setVirtServerRegistry(VirtServerRegistry virtServerRegistry) + { + this.virtServerRegistry = virtServerRegistry; + } + + public void setPreviewURIServiceRegistry(PreviewURIServiceRegistry previewURIProviderRegistry) + { + this.previewURIProviderRegistry = previewURIProviderRegistry; + } + + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + public void setAvmLockingService(AVMLockingService avmLockingService) + { + this.avmLockingService = avmLockingService; + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.WebProjectService#createWebProject(java.lang.String, java.lang.String, java.lang.String, java.lang.String) + */ + public WebProjectInfo createWebProject(String dnsName, String name, String title, String description) + { + return createWebProject(dnsName, name, title, description, null, false, null); + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.WebProjectService#createWebProject(java.lang.String, java.lang.String, java.lang.String, java.lang.String, org.alfresco.service.cmr.repository.NodeRef) + */ + public WebProjectInfo createWebProject(String dnsName, String name, String title, String description, NodeRef sourceNodeRef) + { + return createWebProject(dnsName, name, title, description, null, false, sourceNodeRef); + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.WebProjectService#createWebProject(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, boolean, org.alfresco.service.cmr.repository.NodeRef) + */ + public WebProjectInfo createWebProject(String dnsName, String name, String title, String description, String defaultWebApp, boolean useAsTemplate, NodeRef sourceNodeRef) + { + return createWebProject(new WebProjectInfoImpl(dnsName, name, title, description, defaultWebApp, useAsTemplate, sourceNodeRef, null)); + } + + public WebProjectInfo createWebProject(WebProjectInfo wpInfo) + { + long start = System.currentTimeMillis(); + + String wpStoreId = wpInfo.getStoreId(); + String name = wpInfo.getName(); + String title = wpInfo.getTitle(); + String description = wpInfo.getDescription(); + boolean useAsTemplate = wpInfo.isTemplate(); + NodeRef sourceNodeRef = wpInfo.getNodeRef(); + String defaultWebApp = wpInfo.getDefaultWebApp(); + String previewProviderName = wpInfo.getPreviewProviderName(); + + ParameterCheck.mandatoryString("wpStoreId", wpStoreId); + ParameterCheck.mandatoryString("name", name); + + // Generate web project store id (an AVM store name) + wpStoreId = DNSNameMangler.MakeDNSName(wpStoreId); + + if (wpStoreId.indexOf(WCMUtil.STORE_SEPARATOR) != -1) + { + throw new IllegalArgumentException("Unexpected store id '"+wpStoreId+"' - should not contain '"+WCMUtil.STORE_SEPARATOR+"'"); + } + + if (wpStoreId.indexOf(AVMUtil.AVM_STORE_SEPARATOR_CHAR) != -1) + { + throw new IllegalArgumentException("Unexpected store id '"+wpStoreId+"' - should not contain '"+AVMUtil.AVM_STORE_SEPARATOR_CHAR+"'"); + } + + if (previewProviderName == null) + { + // default preview URI service provider + previewProviderName = previewURIProviderRegistry.getDefaultProviderName(); + } + else if (! previewURIProviderRegistry.getPreviewURIServiceProviders().keySet().contains(previewProviderName)) + { + throw new AlfrescoRuntimeException("Cannot update web project '" + wpInfo.getStoreId() + "' - unknown preview URI service provider ("+previewProviderName+")"); + } + + // default webapp name + defaultWebApp = (defaultWebApp != null && defaultWebApp.length() != 0) ? defaultWebApp : WCMUtil.DIR_ROOT; + + // create the website space in the correct parent folder + Map props = new HashMap(1); + props.put(ContentModel.PROP_NAME, name); + props.put(WCMAppModel.PROP_ISSOURCE, useAsTemplate); + props.put(WCMAppModel.PROP_DEFAULTWEBAPP, defaultWebApp); + props.put(WCMAppModel.PROP_AVMSTORE, wpStoreId); // reference to the root AVM store + props.put(WCMAppModel.PROP_PREVIEW_PROVIDER, previewProviderName); + + NodeRef webProjectsRoot = getWebProjectsRoot(); + + // ALF-906: ensure that DM rules are not inherited by web projects + if(!nodeService.hasAspect(webProjectsRoot, RuleModel.ASPECT_IGNORE_INHERITED_RULES)) + { + nodeService.addAspect(webProjectsRoot, RuleModel.ASPECT_IGNORE_INHERITED_RULES, null); + } + + ChildAssociationRef childAssocRef = nodeService.createNode( + webProjectsRoot, + ContentModel.ASSOC_CONTAINS, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, QName.createValidLocalName(name)), + WCMAppModel.TYPE_AVMWEBFOLDER, + props); + + NodeRef wpNodeRef = childAssocRef.getChildRef(); + + + // apply the uifacets aspect - icon, title and description props + Map uiFacetsProps = new HashMap(4); + uiFacetsProps.put(ApplicationModel.PROP_ICON, WCMUtil.SPACE_ICON_WEBSITE); + uiFacetsProps.put(ContentModel.PROP_TITLE, title); + uiFacetsProps.put(ContentModel.PROP_DESCRIPTION, description); + nodeService.addAspect(wpNodeRef, ApplicationModel.ASPECT_UIFACETS, uiFacetsProps); + + // branch from source web project, if supplied + String branchStoreId = null; + if (sourceNodeRef != null) + { + branchStoreId = (String)nodeService.getProperty(sourceNodeRef, WCMAppModel.PROP_AVMSTORE); + } + + // create the AVM staging store to represent the newly created location website + sandboxFactory.createStagingSandbox(wpStoreId, wpNodeRef, branchStoreId); // ignore return, fails if web project already exists + + String stagingStore = WCMUtil.buildStagingStoreName(wpStoreId); + + // create the default webapp folder under the hidden system folders + if (branchStoreId == null) + { + String stagingStoreRoot = WCMUtil.buildSandboxRootPath(stagingStore); + avmService.createDirectory(stagingStoreRoot, defaultWebApp); + avmService.addAspect(AVMNodeConverter.ExtendAVMPath(stagingStoreRoot, defaultWebApp), WCMAppModel.ASPECT_WEBAPP); + } + + // now the sandbox is created set the permissions masks for the store + sandboxFactory.setStagingPermissionMasks(wpStoreId); + + // set preview provider on staging store (used for preview lookup) + avmService.setStoreProperty(stagingStore, + SandboxConstants.PROP_WEB_PROJECT_PREVIEW_PROVIDER, + new PropertyValue(DataTypeDefinition.TEXT, previewProviderName)); + + // Snapshot the store with the empty webapp + avmService.createSnapshot(wpStoreId, null, null); + + // break the permissions inheritance on the web project node so that only assigned users can access it + permissionService.setInheritParentPermissions(wpNodeRef, false); + + // TODO: Currently auto-creates author sandbox for creator of web project (eg. an admin or a DM contributor to web projects root space) + // NOTE: JSF client does not yet allow explicit creation of author sandboxes + inviteWebUser(wpNodeRef, AuthenticationUtil.getFullyAuthenticatedUser(), WCMUtil.ROLE_CONTENT_MANAGER, true); + + // Bind the post-commit transaction listener with data required for virtualization server notification + CreateWebAppTransactionListener tl = new CreateWebAppTransactionListener(wpStoreId, WCMUtil.DIR_ROOT); + AlfrescoTransactionSupport.bindListener(tl); + + if (logger.isDebugEnabled()) + { + logger.debug("Created web project: " + wpNodeRef + " in "+(System.currentTimeMillis()-start)+" ms (store id: " + wpStoreId + ")"); + } + + // Return created web project info + return new WebProjectInfoImpl(wpStoreId, name, title, description, defaultWebApp, useAsTemplate, wpNodeRef, previewProviderName); + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.webproject.WebProjectService#createWebApp(java.lang.String, java.lang.String, java.lang.String) + */ + public void createWebApp(String wpStoreId, String webAppName, String webAppDescription) + { + createWebApp(getWebProjectNodeFromStore(wpStoreId), webAppName, webAppDescription); + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.webproject.WebProjectService#createWebApp(org.alfresco.service.cmr.repository.NodeRef, java.lang.String, java.lang.String) + */ + public void createWebApp(NodeRef wpNodeRef, final String webAppName, final String webAppDescription) + { + long start = System.currentTimeMillis(); + + WebProjectInfo wpInfo = getWebProject(wpNodeRef); + + if (isContentManager(wpNodeRef)) + { + // get AVM store name of the staging sandbox + final String stagingStoreId = wpInfo.getStagingStoreName(); + + AuthenticationUtil.runAs(new RunAsWork() + { + public Object doWork() throws Exception + { + final String parent = WCMUtil.buildSandboxRootPath(stagingStoreId); + avmService.createDirectory(parent, webAppName); + + String path = AVMNodeConverter.ExtendAVMPath(parent, webAppName); + avmService.addAspect(path, ApplicationModel.ASPECT_UIFACETS); + avmService.addAspect(path, WCMAppModel.ASPECT_WEBAPP); + + if (webAppDescription != null && webAppDescription.length() != 0) + { + avmService.setNodeProperty(path, + ContentModel.PROP_DESCRIPTION, + new PropertyValue(DataTypeDefinition.TEXT, + webAppDescription)); + } + + // Snapshot the store with the empty webapp + avmService.createSnapshot(stagingStoreId, null, null); + + return null; + } + }, AuthenticationUtil.getSystemUserName()); + + CreateWebAppTransactionListener tl = new CreateWebAppTransactionListener(wpInfo.getStoreId(), webAppName); + AlfrescoTransactionSupport.bindListener(tl); + + if (logger.isDebugEnabled()) + { + logger.debug("Created web app: "+webAppName+" in "+(System.currentTimeMillis()-start)+" ms (store id: "+wpInfo.getStoreId()+")"); + } + } + else + { + throw new AccessDeniedException("Only content managers may create new webapp '"+webAppName+"' (store id: "+wpInfo.getStoreId()+")"); + } + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.webproject.WebProjectService#listWebApps(java.lang.String) + */ + public List listWebApps(String wpStoreId) + { + return listWebApps(getWebProjectNodeFromStore(wpStoreId)); + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.webproject.WebProjectService#listWebApps(org.alfresco.service.cmr.repository.NodeRef) + */ + public List listWebApps(NodeRef wpNodeRef) + { + WebProjectInfo wpInfo = getWebProject(wpNodeRef); + + String path = WCMUtil.buildSandboxRootPath(wpInfo.getStagingStoreName()); + Map folders = avmService.getDirectoryListing(-1, path); + + List webAppNames = new ArrayList(folders.size()); + webAppNames.addAll(folders.keySet()); + return webAppNames; + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.webproject.WebProjectService#deleteWebApp(java.lang.String, java.lang.String) + */ + public void deleteWebApp(String wpStoreId, String webAppName) + { + deleteWebApp(getWebProjectNodeFromStore(wpStoreId), webAppName); + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.webproject.WebProjectService#deleteWebApp(org.alfresco.service.cmr.repository.NodeRef, java.lang.String) + */ + public void deleteWebApp(NodeRef wpNodeRef, final String webAppName) + { + long start = System.currentTimeMillis(); + + ParameterCheck.mandatoryString("webAppName", webAppName); + + WebProjectInfo wpInfo = getWebProject(wpNodeRef); + + if (webAppName.equals(wpInfo.getDefaultWebApp())) + { + throw new AlfrescoRuntimeException("Cannot delete default webapp '"+webAppName+"' (store id: "+wpInfo.getStoreId()+")"); + } + else if (isContentManager(wpInfo.getNodeRef())) + { + // get AVM store name of the staging sandbox + final String wpStoreId = wpInfo.getStoreId(); + + WCMUtil.removeVServerWebapp(virtServerRegistry, WCMUtil.buildStoreWebappPath(wpStoreId, webAppName), true); + + AuthenticationUtil.runAs(new RunAsWork() + { + public Object doWork() throws Exception + { + final String parent = WCMUtil.buildSandboxRootPath(wpStoreId); + + avmService.removeNode(parent, webAppName); + + // Snapshot the store with the webapp removed + avmService.createSnapshot(wpStoreId, null, null); + + return null; + } + }, AuthenticationUtil.getSystemUserName()); + + if (logger.isDebugEnabled()) + { + logger.debug("Deleted web app: "+webAppName+" in "+(System.currentTimeMillis()-start)+" ms (store id: "+wpStoreId+")"); + } + } + else + { + throw new AccessDeniedException("Only content managers may delete webapp '"+webAppName+"' (web project: "+wpNodeRef+")"); + } + } + + /* + * @see org.alfresco.wcm.webproject.WebProjectService#hasWebProjectsRoot() + */ + public boolean hasWebProjectsRoot() + { + return getWebProjectsRootOrNull() != null; + } + + private NodeRef getWebProjectsRootOrNull() + { + if (!this.isSetWebProjectsRootNodeRef) + { + // Get the root 'web projects' folder + List results = this.searchService.selectNodes(this.nodeService.getRootNode(WEBPROJECT_STORE), + getWebProjectsPath(), null, this.namespaceService, false); + int size = results.size(); + if (size > 1) + { + // More than one root web projects folder exits + throw new AlfrescoRuntimeException("More than one root 'Web Projects' folder exists"); + } + if (size > 0) + { + this.webProjectsRootNodeRef = results.get(0); + } + this.isSetWebProjectsRootNodeRef = true; + } + + return this.webProjectsRootNodeRef; + } + + /** + * Get the node reference that is the web projects root + * + * @return NodeRef node reference + */ + public NodeRef getWebProjectsRoot() + { + NodeRef result = getWebProjectsRootOrNull(); + if (result == null) + { + // No root web projects folder exists + throw new AlfrescoRuntimeException("No root 'Web Projects' folder exists (is WCM enabled ?)"); + } + return result; + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.webproject.WebProjectService#listWebProjects() + */ + public List listWebProjects() + { + NodeRef wpRoot = getWebProjectsRoot(); + + Set nodeTypeQNames = new HashSet(1); + nodeTypeQNames.add(WCMAppModel.TYPE_AVMWEBFOLDER); + List webProjects = nodeService.getChildAssocs(wpRoot, nodeTypeQNames); + + List result = new ArrayList(webProjects.size()); + for (ChildAssociationRef childAssocRefs : webProjects) + { + result.add(getWebProject(childAssocRefs.getChildRef())); + } + + return result; + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.webproject.WebProjectService#listWebProjects(java.lang.String) + */ + public List listWebProjects(String userName) + { + List webProjects = listWebProjects(); + List result = new ArrayList(webProjects.size()); + for (WebProjectInfo webProject : webProjects) + { + if (isWebUser(webProject.getNodeRef(), userName) == true) + { + result.add(webProject); + } + } + return result; + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.webproject.WebProjectService#isWebProject(java.lang.String) + */ + public boolean isWebProject(String wpStoreId) + { + NodeRef wpNodeRef = getWebProjectNodeFromStore(wpStoreId); + if (wpNodeRef == null) + { + return false; + } + return isWebProject(wpNodeRef); + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.webproject.WebProjectService#isWebProject(org.alfresco.service.cmr.repository.NodeRef) + */ + public boolean isWebProject(NodeRef wpNodeRef) + { + if (wpNodeRef == null) + { + return false; + } + + try + { + return (WCMAppModel.TYPE_AVMWEBFOLDER.equals(nodeService.getType(wpNodeRef))); + } + catch (InvalidNodeRefException e) + { + return false; + } + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.webproject.WebProjectService#getWebProject(java.lang.String) + */ + public WebProjectInfo getWebProject(String wpStoreId) + { + WebProjectInfo result = null; + + // Get the web project node + NodeRef wpNodeRef = getWebProjectNodeFromStore(wpStoreId); + if (wpNodeRef != null) + { + // Create the web project info + result = getWebProject(wpNodeRef); + } + + // Return the web project info + return result; + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.webproject.WebProjectService#getPreviewProvider(java.lang.String) + */ + public String getPreviewProvider(String wpStoreId) + { + ParameterCheck.mandatoryString("wpStoreId", wpStoreId); + + String previewProviderName = null; + + try + { + String stagingStoreId = WCMUtil.buildStagingStoreName(wpStoreId); + PropertyValue pValue = avmService.getStoreProperty(stagingStoreId, SandboxConstants.PROP_WEB_PROJECT_PREVIEW_PROVIDER); + + if (pValue != null) + { + previewProviderName = (String)pValue.getValue(DataTypeDefinition.TEXT); + } + } + catch (AVMNotFoundException nfe) + { + logger.warn(wpStoreId + " is not a web project: " + nfe); + } + + if (previewProviderName == null) + { + previewProviderName = previewURIProviderRegistry.getDefaultProviderName(); + } + + return previewProviderName; + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.webproject.WebProjectService#getWebProject(org.alfresco.service.cmr.repository.NodeRef) + */ + public WebProjectInfo getWebProject(NodeRef wpNodeRef) + { + if (! isWebProject(wpNodeRef)) + { + throw new IllegalArgumentException(wpNodeRef + " is not a web project"); + } + + // Get the properties + Map properties = this.nodeService.getProperties(wpNodeRef); + + String name = (String)properties.get(ContentModel.PROP_NAME); + String title = (String)properties.get(ContentModel.PROP_TITLE); + String description = (String)properties.get(ContentModel.PROP_DESCRIPTION); + String wpStoreId = (String)properties.get(WCMAppModel.PROP_AVMSTORE); + String defaultWebApp = (String)properties.get(WCMAppModel.PROP_DEFAULTWEBAPP); + Boolean useAsTemplate = (Boolean)properties.get(WCMAppModel.PROP_ISSOURCE); + String previewProvider = (String)properties.get(WCMAppModel.PROP_PREVIEW_PROVIDER); + + // Create and return the web project info + WebProjectInfo wpInfo = new WebProjectInfoImpl(wpStoreId, name, title, description, defaultWebApp, useAsTemplate, wpNodeRef, previewProvider); + return wpInfo; + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.webproject.WebProjectService#updateWebProject(org.alfresco.wcm.webproject.WebProjectInfo) + */ + public void updateWebProject(WebProjectInfo wpInfo) + { + long start = System.currentTimeMillis(); + + NodeRef wpNodeRef = getWebProjectNodeFromStore(wpInfo.getStoreId()); + if (wpNodeRef == null) + { + throw new AlfrescoRuntimeException("Cannot update web project '" + wpInfo.getStoreId() + "' - it does not exist."); + } + + if (! listWebApps(wpNodeRef).contains(wpInfo.getDefaultWebApp())) + { + throw new AlfrescoRuntimeException("Cannot update web project '" + wpInfo.getStoreId() + "' - unknown default web app ("+wpInfo.getDefaultWebApp()+")"); + } + + if (wpInfo.getPreviewProviderName() == null) + { + wpInfo.setPreviewProviderName(previewURIProviderRegistry.getDefaultProviderName()); + } + else if (! previewURIProviderRegistry.getPreviewURIServiceProviders().keySet().contains(wpInfo.getPreviewProviderName())) + { + throw new AlfrescoRuntimeException("Cannot update web project '" + wpInfo.getStoreId() + "' - unknown preview URI service provider ("+wpInfo.getPreviewProviderName()+")"); + } + + // Note: the site preset and short name can not be updated + + // Update the properties of the site - note: cannot change storeId or wpNodeRef + Map properties = this.nodeService.getProperties(wpNodeRef); + + properties.put(ContentModel.PROP_NAME, wpInfo.getName()); + properties.put(ContentModel.PROP_TITLE, wpInfo.getTitle()); + properties.put(ContentModel.PROP_DESCRIPTION, wpInfo.getDescription()); + properties.put(WCMAppModel.PROP_DEFAULTWEBAPP, wpInfo.getDefaultWebApp()); + properties.put(WCMAppModel.PROP_ISSOURCE, wpInfo.isTemplate()); + properties.put(WCMAppModel.PROP_PREVIEW_PROVIDER, wpInfo.getPreviewProviderName()); + + this.nodeService.setProperties(wpNodeRef, properties); + + // set preview provider on staging store (used for preview lookup) + String stagingStore = WCMUtil.buildStagingStoreName(wpInfo.getStoreId()); + + avmService.deleteStoreProperty(stagingStore, SandboxConstants.PROP_WEB_PROJECT_PREVIEW_PROVIDER); + avmService.setStoreProperty(stagingStore, + SandboxConstants.PROP_WEB_PROJECT_PREVIEW_PROVIDER, + new PropertyValue(DataTypeDefinition.TEXT, wpInfo.getPreviewProviderName())); + + if (logger.isDebugEnabled()) + { + logger.debug("Updated web project: " + wpNodeRef + " in "+(System.currentTimeMillis()-start)+" ms (store id: " + wpInfo.getStoreId() + ")"); + } + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.webproject.WebProjectService#deleteWebProject(java.lang.String) + */ + public void deleteWebProject(String wpStoreId) + { + NodeRef wpNodeRef = getWebProjectNodeFromStore(wpStoreId); + if (wpNodeRef != null) + { + deleteWebProject(wpNodeRef); + } + else + { + // by definition, the current user is not a content manager since the web project does not exist (or is not visible) + throw new AccessDeniedException("Only content managers may delete a web project"); + } + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.webproject.WebProjectService#deleteWebProject(org.alfresco.service.cmr.repository.NodeRef) + */ + public void deleteWebProject(final NodeRef wpNodeRef) + { + long start = System.currentTimeMillis(); + + if (! isContentManager(wpNodeRef)) + { + // the current user is not a content manager since the web project does not exist (or is not visible) + throw new AccessDeniedException("Only content managers may delete web project"); + } + + // delete all attached website sandboxes in reverse order to the layering + final String wpStoreId = (String)nodeService.getProperty(wpNodeRef, WCMAppModel.PROP_AVMSTORE); + + if (wpStoreId != null) + { + // Notify virtualization server about removing this website + // + // Implementation note: + // + // Because the removal of virtual webapps in the virtualization + // server is recursive, it only needs to be given the name of + // the main staging store. + // + // This notification must occur *prior* to purging content + // within the AVM because the virtualization server must list + // the avm_webapps dir in each store to discover which + // virtual webapps must be unloaded. The virtualization + // server traverses the sandbox's stores in most-to-least + // dependent order, so clients don't have to worry about + // accessing a preview layer whose main layer has been torn + // out from under it. + // + // It does not matter what webapp name we give here, so "/ROOT" + // is as sensible as anything else. It's all going away. + + final String sandbox = WCMUtil.buildStagingStoreName(wpStoreId); + String path = WCMUtil.buildStoreWebappPath(sandbox, "/ROOT"); + + WCMUtil.removeAllVServerWebapps(virtServerRegistry, path, true); + + try + { + RetryingTransactionCallback deleteWebProjectWork = new RetryingTransactionCallback() + { + public Object execute() throws Throwable + { + AuthenticationUtil.runAs(new RunAsWork() + { + public Object doWork() throws Exception + { + List sbInfos = sandboxFactory.listAllSandboxes(wpStoreId, true, true); + + for (SandboxInfo sbInfo : sbInfos) + { + String sbStoreId = sbInfo.getSandboxId(); + + if (WCMUtil.isLocalhostDeployedStore(wpStoreId, sbStoreId)) + { + if (getWebProject(WCMUtil.getWebProjectStoreId(sbStoreId)) != null) + { + continue; + } + } + + // delete sandbox (and associated preview sandbox, if it exists) + sandboxFactory.deleteSandbox(sbInfo, false, false); + } + + // delete all web project locks in one go (ie. all those currently held against staging store) + avmLockingService.removeLocks(wpStoreId); + + StoreRef archiveStoreRef = nodeService.getStoreArchiveNode(wpNodeRef.getStoreRef()).getStoreRef(); + + // delete the web project node itself + nodeService.deleteNode(wpNodeRef); + nodeService.deleteNode(new NodeRef(archiveStoreRef, wpNodeRef.getId())); + + sandboxFactory.removeGroupsForStore(sandbox); + + return null; + } + }, AuthenticationUtil.getSystemUserName()); + + return null; + } + }; + + transactionService.getRetryingTransactionHelper().doInTransaction(deleteWebProjectWork); + + if (logger.isDebugEnabled()) + { + logger.debug("Deleted web project: " + wpNodeRef + " in "+(System.currentTimeMillis()-start)+" ms (store id: " + wpStoreId + ")"); + } + } + catch (Throwable err) + { + throw new AlfrescoRuntimeException("Failed to delete web project: ", err); + } + } + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.webproject.WebProjectService#isContentManager(java.lang.String) + */ + public boolean isContentManager(String storeName) + { + return isContentManager(storeName, AuthenticationUtil.getFullyAuthenticatedUser()); + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.WebProjectService#isContentManager(java.lang.String, java.lang.String) + */ + public boolean isContentManager(String wpStoreId, String userName) + { + return isContentManager(getWebProjectNodeFromStore(wpStoreId), userName); + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.webproject.WebProjectService#isContentManager(org.alfresco.service.cmr.repository.NodeRef) + */ + public boolean isContentManager(NodeRef wpNodeRef) + { + return isContentManager(wpNodeRef, AuthenticationUtil.getFullyAuthenticatedUser()); + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.webproject.WebProjectService#isContentManager(org.alfresco.service.cmr.repository.NodeRef, java.lang.String) + */ + public boolean isContentManager(NodeRef wpNodeRef, String userName) + { + String userRole = getWebUserRole(wpNodeRef, userName); + return WCMUtil.ROLE_CONTENT_MANAGER.equals(userRole); + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.webproject.WebProjectService#isWebUser(java.lang.String) + */ + public boolean isWebUser(String wpStoreId) + { + return isWebUser(getWebProjectNodeFromStore(wpStoreId)); + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.webproject.WebProjectService#isWebUser(org.alfresco.service.cmr.repository.NodeRef) + */ + public boolean isWebUser(NodeRef wpNodeRef) + { + // note: admin is an implied web user (content manager) although will not appear in listWebUsers unless explicitly invited + return (permissionService.hasPermission(wpNodeRef, PermissionService.READ) == AccessStatus.ALLOWED); + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.webproject.WebProjectService#isWebUser(java.lang.String, java.lang.String) + */ + public boolean isWebUser(String wpStoreId, String username) + { + return isWebUser(getWebProjectNodeFromStore(wpStoreId), username); + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.webproject.WebProjectService#isWebUser(org.alfresco.service.cmr.repository.NodeRef, java.lang.String) + */ + public boolean isWebUser(final NodeRef wpNodeRef, String userName) + { + return AuthenticationUtil.runAs(new RunAsWork() + { + public Boolean doWork() throws Exception + { + return isWebUser(wpNodeRef); + } + }, userName); + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.webproject.WebProjectService#getWebUserCount(org.alfresco.service.cmr.repository.NodeRef) + */ + public int getWebUserCount(NodeRef wpNodeRef) + { + long start = System.currentTimeMillis(); + + int cnt = WCMUtil.listWebUserRefs(nodeService, wpNodeRef, false).size(); + + if (logger.isTraceEnabled()) + { + logger.trace("Get web user cnt: " + wpNodeRef + "(" + cnt + ") in "+(System.currentTimeMillis()-start)+" ms"); + } + + return cnt; + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.webproject.WebProjectService#listWebUsers(java.lang.String) + */ + public Map listWebUsers(String wpStoreId) + { + return listWebUsers(getWebProjectNodeFromStore(wpStoreId)); + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.webproject.WebProjectService#listWebUsers(org.alfresco.service.cmr.repository.NodeRef) + */ + public Map listWebUsers(NodeRef wpNodeRef) + { + long start = System.currentTimeMillis(); + + // special case: allow System - eg. to allow user to create their own sandbox on-demand (createAuthorSandbox) + if (isContentManager(wpNodeRef) + || (AuthenticationUtil.getRunAsUser().equals(AuthenticationUtil.getSystemUserName()) + || (permissionService.hasPermission(wpNodeRef, PermissionService.ADD_CHILDREN) == AccessStatus.ALLOWED))) + { + Map users = WCMUtil.listWebUsers(nodeService, wpNodeRef); + + if (logger.isTraceEnabled()) + { + logger.trace("List web users: " + wpNodeRef + "(" + users.size() + ") in "+(System.currentTimeMillis()-start)+" ms"); + } + + return users; + } + else + { + throw new AccessDeniedException("Only content managers may list users in a web project"); + } + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.webproject.WebProjectService#getWebUserRole(java.lang.String, java.lang.String) + */ + public String getWebUserRole(String wpStoreId, String userName) + { + return getWebUserRole(getWebProjectNodeFromStore(wpStoreId), userName); + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.webproject.WebProjectService#getWebUserRole(org.alfresco.service.cmr.repository.NodeRef, java.lang.String) + */ + public String getWebUserRole(NodeRef wpNodeRef, String userName) + { + ParameterCheck.mandatoryString("userName", userName); + + String userRole = null; + + if (! isWebProject(wpNodeRef)) + { + logger.warn(wpNodeRef + " is not a web project"); + return null; + } + + if (authorityService.isAdminAuthority(userName)) + { + // fake the Content Manager role for an admin user + userRole = WCMUtil.ROLE_CONTENT_MANAGER; + } + else + { + userRole = getWebUserRoleImpl(wpNodeRef, userName); + } + + return userRole; + } + + private String getWebUserRoleImpl(NodeRef wpNodeRef, String userName) + { + NodeRef userRef = getWebUserRef(wpNodeRef, userName); + String userRole = null; + + if (userRef != null) + { + userRole = (String)nodeService.getProperty(userRef, WCMAppModel.PROP_WEBUSERROLE); + } + + return userRole; + } + + private NodeRef getWebUserRef(NodeRef wpNodeRef, String userName) + { + StringBuilder query = new StringBuilder(128); + query.append("+PARENT:\"").append(wpNodeRef).append("\" "); + query.append("+TYPE:\"").append(WCMAppModel.TYPE_WEBUSER).append("\" "); + query.append("+@").append(NamespaceService.WCMAPP_MODEL_PREFIX).append("\\:username:\""); + query.append(userName); + query.append("\""); + + ResultSet resultSet = null; + List nodes = null; + try + { + resultSet = searchService.query( + WEBPROJECT_STORE, + SearchService.LANGUAGE_LUCENE, + query.toString()); + nodes = resultSet.getNodeRefs(); + } + finally + { + if (resultSet != null) + { + resultSet.close(); + } + } + + // Lucene indexing may strip certain international characters or treat them as equivalent so we do string + // comparisons on the results to ensure an exact match + Iterator i = nodes.iterator(); + while (i.hasNext()) + { + if (!nodeService.getProperty(i.next(), WCMAppModel.PROP_WEBUSERNAME).equals(userName)) + { + i.remove(); + } + } + + if (nodes.size() == 1) + { + return nodes.get(0); + } + else if (nodes.size() == 0) + { + if (logger.isTraceEnabled()) + { + logger.trace("getWebUserRef: web user ("+userName+") not found in web project: "+wpNodeRef); + } + } + else + { + logger.error("getWebUserRef: more than one web user ("+userName+") found in web project: "+wpNodeRef); + } + + return null; + } + + + /* (non-Javadoc) + * @see org.alfresco.wcm.webproject.WebProjectService#findWebProjectNodeFromPath(java.lang.String) + */ + public NodeRef getWebProjectNodeFromPath(String absoluteAVMPath) + { + return getWebProjectNodeFromStore(WCMUtil.getWebProjectStoreIdFromPath(absoluteAVMPath)); + } + + /*(non-Javadoc) + * @see org.alfresco.wcm.webproject.WebProjectService#getWebProjectNodeFromStore(java.lang.String) + */ + public NodeRef getWebProjectNodeFromStore(String wpStoreId) + { + ParameterCheck.mandatoryString("wpStoreId", wpStoreId); + + return WCMUtil.getWebProjectNodeFromWebProjectStore(avmService, wpStoreId); + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.webproject.WebProjectService#inviteWebUsersGroups(java.lang.String, java.util.Map) + */ + public void inviteWebUsersGroups(String wpStoreId, Map userGroupRoles) + { + inviteWebUsersGroups(getWebProjectNodeFromStore(wpStoreId), userGroupRoles, false); + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.webproject.WebProjectService#inviteWebUsersGroups(java.lang.String, java.util.Map, boolean) + */ + public void inviteWebUsersGroups(String wpStoreId, Map userGroupRoles, boolean autoCreateAuthorSandbox) + { + inviteWebUsersGroups(getWebProjectNodeFromStore(wpStoreId), userGroupRoles, autoCreateAuthorSandbox); + } + + public void inviteWebUsersGroups(NodeRef wpNodeRef, Map userGroupRoles, boolean autoCreateAuthorSandbox) + { + long start = System.currentTimeMillis(); + + if (! (isContentManager(wpNodeRef) || + permissionService.hasPermission(wpNodeRef, PermissionService.ADD_CHILDREN) == AccessStatus.ALLOWED)) + { + throw new AccessDeniedException("Only content managers may invite web users"); + } + + WebProjectInfo wpInfo = getWebProject(wpNodeRef); + String wpStoreId = wpInfo.getStoreId(); + + // build a list of managers who will have full permissions on ALL staging areas + List managers = new ArrayList(4); + Map webSiteUsers = new HashMap(8); + List managersToRemove = new LinkedList(); + List usersToUpdate = new LinkedList(); + + // retrieve the list of managers from the existing users + for (Map.Entry userRole : userGroupRoles.entrySet()) + { + String authority = userRole.getKey(); + String role = userRole.getValue(); + + for (String userAuth : findNestedUserAuthorities(authority)) + { + if (WCMUtil.ROLE_CONTENT_MANAGER.equals(role)) + { + managers.add(userAuth); + } + } + } + + List userInfoRefs = nodeService.getChildAssocs(wpNodeRef, WCMAppModel.ASSOC_WEBUSER, RegexQNamePattern.MATCH_ALL); + + for (ChildAssociationRef ref : userInfoRefs) + { + NodeRef userInfoRef = ref.getChildRef(); + String username = (String)nodeService.getProperty(userInfoRef, WCMAppModel.PROP_WEBUSERNAME); + String userrole = (String)nodeService.getProperty(userInfoRef, WCMAppModel.PROP_WEBUSERROLE); + + if (WCMUtil.ROLE_CONTENT_MANAGER.equals(userrole) && managers.contains(username) == false) + { + managers.add(username); + } + + // add each existing user to the map which will be rechecked for update changed user permissions + webSiteUsers.put(username, userInfoRef); + } + + List sandboxInfoList = new LinkedList(); + + int invitedCount = 0; + boolean managersUpdateRequired = false; + + for (Map.Entry userRole : userGroupRoles.entrySet()) + { + String authority = userRole.getKey(); + String role = userRole.getValue(); + + for (String userAuth : findNestedUserAuthorities(authority)) + { + if (webSiteUsers.keySet().contains(userAuth) == false) + { + if (autoCreateAuthorSandbox) + { + // create a sandbox for the user with permissions based on role + SandboxInfo sbInfo = sandboxFactory.createUserSandbox(wpStoreId, userAuth, role); + sandboxInfoList.add(sbInfo); + } + + sandboxFactory.addStagingAreaUser(wpStoreId, userAuth, role); + + // create an app:webuser instance for each authority and assoc to the web project node + createWebUser(wpNodeRef, userAuth, role); + + // if this new user is a manager, we'll need to update the manager permissions applied + // to each existing user sandbox - to ensure that new managers have access to them + managersUpdateRequired |= (WCMUtil.ROLE_CONTENT_MANAGER.equals(role)); + + invitedCount++; + } + else + { + // TODO - split out into separate 'change role' + // if user role have been changed then update required properties etc. + NodeRef userRef = webSiteUsers.get(userAuth); + String oldUserRole = (String)nodeService.getProperty(userRef, WCMAppModel.PROP_WEBUSERROLE); + + if (!role.equals(oldUserRole)) + { + // change in role + Map props = nodeService.getProperties(userRef); + props.put(WCMAppModel.PROP_WEBUSERNAME, userAuth); + props.put(WCMAppModel.PROP_WEBUSERROLE, role); + nodeService.setProperties(userRef, props); + + if (WCMUtil.ROLE_CONTENT_MANAGER.equals(role)) + { + managersUpdateRequired = true; + } + else if (WCMUtil.ROLE_CONTENT_MANAGER.equals(oldUserRole)) + { + managersToRemove.add(userAuth); + } + + usersToUpdate.add(sandboxFactory.new UserRoleWrapper(userAuth, oldUserRole, role)); + + if (logger.isDebugEnabled()) + { + logger.debug(userAuth +"'s role has been changed from '" + oldUserRole + + "' to '" + role + "'"); + } + } + } + } + } + + // Bind the post-commit transaction listener with data required for virtualization server notification + CreateSandboxTransactionListener tl = new CreateSandboxTransactionListener(sandboxInfoList, listWebApps(wpNodeRef)); + AlfrescoTransactionSupport.bindListener(tl); + + if (managersUpdateRequired == true) + { + sandboxFactory.updateSandboxManagers(wpStoreId, managers); + } + + // TODO - split out into separate 'change role' + // remove ex-managers from sandboxes + if (managersToRemove.size() != 0) + { + sandboxFactory.removeSandboxManagers(wpStoreId, managersToRemove); + } + + // get permissions and roles for a web project folder type + Set perms = permissionService.getSettablePermissions(WCMAppModel.TYPE_AVMWEBFOLDER); + + // set permissions for each user + for (Map.Entry userRole : userGroupRoles.entrySet()) + { + String authority = userRole.getKey(); + String role = userRole.getValue(); + + for (String permission : perms) + { + if (role.equals(permission)) + { + permissionService.setPermission(wpNodeRef, + authority, + permission, + true); + break; + } + } + } + + // TODO - split out into separate 'change role' + // update user's roles + if (usersToUpdate.size() != 0) + { + sandboxFactory.updateSandboxRoles(wpStoreId, usersToUpdate, perms); + } + + if (logger.isDebugEnabled()) + { + logger.debug("Invited "+invitedCount+" web users in "+(System.currentTimeMillis()-start)+" ms (store id: "+wpStoreId+")"); + } + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.webproject.WebProjectService#inviteWebUser(java.lang.String, java.lang.String, java.lang.String) + */ + public void inviteWebUser(String wpStoreId, String userAuth, String role) + { + inviteWebUser(getWebProjectNodeFromStore(wpStoreId), userAuth, role, false); + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.webproject.WebProjectService#inviteWebUser(java.lang.String, java.lang.String, java.lang.String, boolean) + */ + public void inviteWebUser(String wpStoreId, String userAuth, String role, boolean autoCreateAuthorSandbox) + { + inviteWebUser(getWebProjectNodeFromStore(wpStoreId), userAuth, role, autoCreateAuthorSandbox); + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.webproject.WebProjectService#inviteWebUser(org.alfresco.service.cmr.repository.NodeRef, java.lang.String, java.lang.String, boolean) + */ + public void inviteWebUser(NodeRef wpNodeRef, String userAuth, String role, boolean autoCreateAuthorSandbox) + { + long start = System.currentTimeMillis(); + + if (! (isContentManager(wpNodeRef) || + permissionService.hasPermission(wpNodeRef, PermissionService.ADD_CHILDREN) == AccessStatus.ALLOWED)) + { + throw new AccessDeniedException("Only content managers may invite web user"); + } + + WebProjectInfo wpInfo = getWebProject(wpNodeRef); + final String wpStoreId = wpInfo.getStoreId(); + + // build a list of managers who will have full permissions on ALL staging areas + List managers = new ArrayList(4); + + // retrieve the list of managers from the existing users + Map existingUserRoles = listWebUsers(wpNodeRef); + for (Map.Entry userRole : existingUserRoles.entrySet()) + { + String username = userRole.getKey(); + String userrole = userRole.getValue(); + + if (WCMUtil.ROLE_CONTENT_MANAGER.equals(userrole) && managers.contains(username) == false) + { + managers.add(username); + } + } + + // get permissions and roles for a web project folder type + Set perms = permissionService.getSettablePermissions(WCMAppModel.TYPE_AVMWEBFOLDER); + + NodeRef userRef = getWebUserRef(wpNodeRef, userAuth); + if (userRef != null) + { + // TODO - split out into separate 'change role' + // if user role has been changed then update required properties etc. + String oldUserRole = (String)nodeService.getProperty(userRef, WCMAppModel.PROP_WEBUSERROLE); + if (!role.equals(oldUserRole)) + { + // change in role + Map props = nodeService.getProperties(userRef); + props.put(WCMAppModel.PROP_WEBUSERNAME, userAuth); + props.put(WCMAppModel.PROP_WEBUSERROLE, role); + nodeService.setProperties(userRef, props); + + if (WCMUtil.ROLE_CONTENT_MANAGER.equals(role)) + { + managers.add(userAuth); + sandboxFactory.updateSandboxManagers(wpStoreId, managers); + } + else if (WCMUtil.ROLE_CONTENT_MANAGER.equals(oldUserRole)) + { + List managersToRemove = new LinkedList(); + managersToRemove.add(userAuth); + + sandboxFactory.removeSandboxManagers(wpStoreId, managersToRemove); + } + + List usersToUpdate = new LinkedList(); + usersToUpdate.add(sandboxFactory.new UserRoleWrapper(userAuth, oldUserRole, role)); + + sandboxFactory.updateSandboxRoles(wpStoreId, usersToUpdate, perms); + + if (logger.isDebugEnabled()) + { + logger.debug("Web user "+userAuth +"'s role has been changed from '" + oldUserRole + "' to '" + role + "' in "+(System.currentTimeMillis()-start)+" ms (store id: "+wpStoreId+")"); + } + } + } + else + { + if (autoCreateAuthorSandbox) + { + // create a sandbox for the user with permissions based on role + SandboxInfo sbInfo = sandboxFactory.createUserSandbox(wpStoreId, userAuth, role); + + List sandboxInfoList = new LinkedList(); + sandboxInfoList.add(sbInfo); + + // Bind the post-commit transaction listener with data required for virtualization server notification + CreateSandboxTransactionListener tl = new CreateSandboxTransactionListener(sandboxInfoList, listWebApps(wpNodeRef)); + AlfrescoTransactionSupport.bindListener(tl); + } + + // if this new user is a manager, we'll need to update the manager permissions applied + // to each existing user sandbox - to ensure that new user has access to them + if (WCMUtil.ROLE_CONTENT_MANAGER.equals(role)) + { + managers.add(userAuth); + sandboxFactory.updateSandboxManagers(wpStoreId, managers); + } + + sandboxFactory.addStagingAreaUser(wpStoreId, userAuth, role); + + // create an app:webuser instance for the user and assoc to the web project node + createWebUser(wpNodeRef, userAuth, role); + + // set permissions for the user + for (String permission : perms) + { + if (role.equals(permission)) + { + permissionService.setPermission(wpNodeRef, + userAuth, + permission, + true); + break; + } + } + + if (logger.isDebugEnabled()) + { + logger.debug("Invited web user: "+userAuth+" in "+(System.currentTimeMillis()-start)+" ms (store id: "+wpStoreId+")"); + } + } + } + + private void createWebUser(NodeRef wpNodeRef, String userName, String userRole) + { + // create an app:webuser instance for the user and assoc to the web project node + Map props = new HashMap(2, 1.0f); + props.put(WCMAppModel.PROP_WEBUSERNAME, userName); + props.put(WCMAppModel.PROP_WEBUSERROLE, userRole); + nodeService.createNode(wpNodeRef, + WCMAppModel.ASSOC_WEBUSER, + WCMAppModel.ASSOC_WEBUSER, + WCMAppModel.TYPE_WEBUSER, + props); + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.webproject.WebProjectService#uninviteWebUser(java.lang.String, java.lang.String) + */ + public void uninviteWebUser(String wpStoreId, String userAuth) + { + uninviteWebUser(getWebProjectNodeFromStore(wpStoreId), userAuth, false); + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.webproject.WebProjectService#uninviteWebUser(java.lang.String, java.lang.String, boolean) + */ + public void uninviteWebUser(String wpStoreId, String userAuth, boolean autoDeleteAuthorSandbox) + { + uninviteWebUser(getWebProjectNodeFromStore(wpStoreId), userAuth, autoDeleteAuthorSandbox); + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.webproject.WebProjectService#uninviteWebUser(org.alfresco.service.cmr.repository.NodeRef, java.lang.String, boolean) + */ + public void uninviteWebUser(NodeRef wpNodeRef, String userAuth, boolean autoDeleteAuthorSandbox) + { + long start = System.currentTimeMillis(); + + if (! isContentManager(wpNodeRef)) + { + throw new AccessDeniedException("Only content managers may uninvite web user '"+userAuth+"' from web project: "+wpNodeRef); + } + + ParameterCheck.mandatory("wpNodeRef", wpNodeRef); + ParameterCheck.mandatoryString("userAuth", userAuth); + + WebProjectInfo wpInfo = getWebProject(wpNodeRef); + String wpStoreId = wpInfo.getStoreId(); + String userMainStore = WCMUtil.buildUserMainStoreName(wpStoreId, userAuth); + + if (autoDeleteAuthorSandbox) + { + sandboxFactory.deleteSandbox(userMainStore); + } + + // remove the store reference from the website folder meta-data (see also WCMUtil.listWebUsers) + List userInfoRefs = nodeService.getChildAssocs(wpNodeRef, WCMAppModel.ASSOC_WEBUSER, RegexQNamePattern.MATCH_ALL); + + // retrieve the list of managers from the existing users + List managers = new ArrayList(4); + for (ChildAssociationRef ref : userInfoRefs) + { + NodeRef userInfoRef = ref.getChildRef(); + String username = (String)nodeService.getProperty(userInfoRef, WCMAppModel.PROP_WEBUSERNAME); + String userrole = (String)nodeService.getProperty(userInfoRef, WCMAppModel.PROP_WEBUSERROLE); + + if (WCMUtil.ROLE_CONTENT_MANAGER.equals(userrole) && managers.contains(username) == false) + { + managers.add(username); + } + } + + for (ChildAssociationRef ref : userInfoRefs) + { + NodeRef userInfoRef = ref.getChildRef(); + String user = (String)nodeService.getProperty(userInfoRef, WCMAppModel.PROP_WEBUSERNAME); + + if (userAuth.equals(user)) + { + // remove the association to this web project user meta-data + nodeService.removeChild(wpNodeRef, ref.getChildRef()); + + // remove permission for the user (also fixes ETWOONE-338) + permissionService.clearPermission(wpNodeRef, userAuth); + + if (logger.isDebugEnabled()) + { + logger.debug("Uninvited web user: "+userAuth+" in "+(System.currentTimeMillis()-start)+" ms (store id: "+wpStoreId+")"); + } + + break; // for loop + } + } + } + + /** + * Find all nested user authorities contained with an authority + * + * @param authority The authority to search, USER authorities are returned immediately, GROUP authorites + * are recursively scanned for contained USER authorities. + * + * @return a Set of USER authorities + */ + private Set findNestedUserAuthorities(String authority) + { + Set users; + + AuthorityType authType = AuthorityType.getAuthorityType(authority); + if (authType.equals(AuthorityType.USER)) + { + users = new HashSet(1, 1.0f); + if (personService.personExists(authority) == true) + { + users.add(authority); + } + } + else if (authType.equals(AuthorityType.GROUP)) + { + // walk each member of the group + users = authorityService.getContainedAuthorities(AuthorityType.USER, authority, false); + for (String userAuth : users) + { + if (personService.personExists(userAuth) == false) + { + users.remove(userAuth); + } + } + } + else + { + users = Collections.emptySet(); + } + + return users; + } + + private String getWebProjectsPath() + { + return "/"+SPACES_COMPANY_HOME_CHILDNAME+"/"+SPACES_WCM_CHILDNAME; + } + + private static final String SPACES_COMPANY_HOME_CHILDNAME = "app:company_home"; // should match repository property: spaces.company_home.childname + private static final String SPACES_WCM_CHILDNAME = "app:wcm"; // should match repository property: spaces.wcm.childname + + + /** + * Create WebProject/WebApp Transaction listener - invoked after commit + */ + private class CreateWebAppTransactionListener extends TransactionListenerAdapter + { + private String wpStoreId; + private String webApp; + + public CreateWebAppTransactionListener(String wpStoreId, String webApp) + { + this.wpStoreId = wpStoreId; + this.webApp = webApp; + } + + /** + * @see org.alfresco.repo.transaction.TransactionListenerAdapter#afterCommit() + */ + @Override + public void afterCommit() + { + // post-commit + if (wpStoreId != null) + { + // update the virtualisation server with webapp + // performed after the main txn has committed successfully + String newStoreName = WCMUtil.buildStagingStoreName(wpStoreId); + + String path = WCMUtil.buildStoreWebappPath(newStoreName, webApp); + + WCMUtil.updateVServerWebapp(virtServerRegistry, path, true); + } + } + } + + /** + * Create Sandbox Transaction listener - invoked after commit + */ + private class CreateSandboxTransactionListener extends TransactionListenerAdapter + { + private List sandboxInfoList; + private List webAppNames; + + public CreateSandboxTransactionListener(List sandboxInfoList, List webAppNames) + { + this.sandboxInfoList = sandboxInfoList; + this.webAppNames = webAppNames; + } + + /** + * @see org.alfresco.repo.transaction.TransactionListenerAdapter#afterCommit() + */ + @Override + public void afterCommit() + { + // Handle notification to the virtualization server + // (this needs to occur after the sandboxes are created in the main txn) + + // reload virtualisation server for webapp(s) in this web project + for (SandboxInfo sandboxInfo : this.sandboxInfoList) + { + String newlyInvitedStoreName = WCMUtil.buildStagingStoreName(sandboxInfo.getMainStoreName()); + + for (String webAppName : webAppNames) + { + String path = WCMUtil.buildStoreWebappPath(newlyInvitedStoreName, webAppName); + WCMUtil.updateVServerWebapp(virtServerRegistry, path, true); + } + } + } + } +} diff --git a/source/test-resources/alfresco/testaudit/alfresco-audit-test-alf-12638.xml b/source/test-resources/alfresco/testaudit/alfresco-audit-test-alf-12638.xml new file mode 100644 index 0000000000..df3cf6ab5e --- /dev/null +++ b/source/test-resources/alfresco/testaudit/alfresco-audit-test-alf-12638.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/test-resources/filesys/ContentComparatorTestExcel2003-1.xls b/source/test-resources/filesys/ContentComparatorTestExcel2003-1.xls new file mode 100644 index 0000000000..6131f0f1a7 Binary files /dev/null and b/source/test-resources/filesys/ContentComparatorTestExcel2003-1.xls differ diff --git a/source/test-resources/filesys/ContentComparatorTestExcel2003-2.xls b/source/test-resources/filesys/ContentComparatorTestExcel2003-2.xls new file mode 100644 index 0000000000..207c287ec7 Binary files /dev/null and b/source/test-resources/filesys/ContentComparatorTestExcel2003-2.xls differ diff --git a/source/test-resources/filesys/ContentComparatorTestExcel2003-3.xls b/source/test-resources/filesys/ContentComparatorTestExcel2003-3.xls new file mode 100644 index 0000000000..dd426454d5 Binary files /dev/null and b/source/test-resources/filesys/ContentComparatorTestExcel2003-3.xls differ diff --git a/source/test-resources/quick/quick.ibooks b/source/test-resources/quick/quick.ibooks new file mode 100644 index 0000000000..10e3f7e5e6 Binary files /dev/null and b/source/test-resources/quick/quick.ibooks differ diff --git a/source/test-resources/schemacomp/xml_to_schema_test.xml b/source/test-resources/schemacomp/xml_to_schema_test.xml index 2eeb2c3ce0..ab4c3a0584 100644 --- a/source/test-resources/schemacomp/xml_to_schema_test.xml +++ b/source/test-resources/schemacomp/xml_to_schema_test.xml @@ -2,7 +2,7 @@ - +