diff --git a/config/alfresco-global.properties.sample b/config/alfresco-global.properties.sample index ae0cbdce3c..2d71726f19 100644 --- a/config/alfresco-global.properties.sample +++ b/config/alfresco-global.properties.sample @@ -63,6 +63,12 @@ #db.url=jdbc:postgresql://localhost:5432/alfresco # +# DB2 connection +# +#db.driver=com.ibm.db2.jcc.DB2Driver +#db.url=jdbc:db2://host:50000/ALFRESCO + +# # Index Recovery Mode #------------- #index.recovery.mode=AUTO diff --git a/config/alfresco/bootstrap-context.xml b/config/alfresco/bootstrap-context.xml index d985d9d7b8..aa0959bf98 100644 --- a/config/alfresco/bootstrap-context.xml +++ b/config/alfresco/bootstrap-context.xml @@ -134,6 +134,7 @@ + diff --git a/config/alfresco/bootstrap/imapSpacesTemplates.acp b/config/alfresco/bootstrap/imapSpacesTemplates.acp index 9e24571951..680891c37e 100644 Binary files a/config/alfresco/bootstrap/imapSpacesTemplates.acp and b/config/alfresco/bootstrap/imapSpacesTemplates.acp differ diff --git a/config/alfresco/cache-context.xml b/config/alfresco/cache-context.xml index 36331fc2da..314a9f26b6 100644 --- a/config/alfresco/cache-context.xml +++ b/config/alfresco/cache-context.xml @@ -594,6 +594,40 @@ + + + + + + + + + + + + + + + org.alfresco.cache.readersDeniedCache + + + + + + + + + + + + + org.alfresco.readersDeniedTransactionalCache + + + + + + diff --git a/config/alfresco/content-services-context.xml b/config/alfresco/content-services-context.xml index 4d8a38b340..985b516203 100644 --- a/config/alfresco/content-services-context.xml +++ b/config/alfresco/content-services-context.xml @@ -153,6 +153,7 @@ classpath:alfresco/mimetype/mimetype-map.xml classpath:alfresco/mimetype/mimetype-map-openoffice.xml + classpath*:alfresco/module/*/mimetype-map*.xml classpath*:alfresco/extension/mimetype/*-map.xml diff --git a/config/alfresco/dao/dao-context.xml b/config/alfresco/dao/dao-context.xml index 87c53182ca..8e7b01d457 100644 --- a/config/alfresco/dao/dao-context.xml +++ b/config/alfresco/dao/dao-context.xml @@ -285,6 +285,7 @@ + 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 index 3c662ea50c..4567af8875 100644 --- 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 @@ -1034,6 +1034,11 @@ BYTEARRAY_ID_ + + + TASK_ID_ + + @@ -1218,6 +1223,11 @@ NAME_ + + + TASK_ID_ + +
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 index 893b0908ec..ef8d5020b6 100644 --- 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 @@ -60,12 +60,17 @@ falsefalse - + varchar(160) false false - + + varchar(160) + false + false + + bigint false false @@ -73,7 +78,7 @@ - name + lc_name parent_id @@ -100,6 +105,12 @@ parent_id + + + lc_name + parent_id + +
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 index c58389858a..3a30395880 100644 --- 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 @@ -449,7 +449,7 @@ - + proc_def_id_ business_key_ @@ -898,7 +898,7 @@ - + proc_def_id_ business_key_ 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 index 4f879ff503..4434aca4e0 100644 --- 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 @@ -64,12 +64,17 @@ false false - + varchar(160) false false - + + varchar(160) + false + false + + int8 false false @@ -78,7 +83,7 @@ parent_id - name + lc_name @@ -104,6 +109,12 @@ parent_id + + + lc_name + parent_id + +
@@ -708,7 +719,7 @@ - + version_id avm_store_id diff --git a/config/alfresco/dbscripts/upgrade/4.0/org.hibernate.dialect.Dialect/ActivitiTaskIdIndexes.sql b/config/alfresco/dbscripts/upgrade/4.0/org.hibernate.dialect.Dialect/ActivitiTaskIdIndexes.sql new file mode 100644 index 0000000000..b77b6fe491 --- /dev/null +++ b/config/alfresco/dbscripts/upgrade/4.0/org.hibernate.dialect.Dialect/ActivitiTaskIdIndexes.sql @@ -0,0 +1,26 @@ +-- +-- Title: Add missing Activiti indexes on task-id for runtime and history variables. +-- Database: Generic +-- Since: V4.0 Schema 5029 +-- Author: Frederik Heremans +-- +-- Please contact support@alfresco.com if you need assistance with the upgrade. +-- + +-- Add index to runtime variable table +create index ACT_IDX_VARIABLE_TASK_ID on ACT_RU_VARIABLE(TASK_ID_); + +-- Add index to history variable table +create index ACT_IDX_HI_DETAIL_TASK_ID on ACT_HI_DETAIL(TASK_ID_); + +-- +-- Record script finish +-- +DELETE FROM alf_applied_patch WHERE id = 'patch.db-V4.0-Activiti-task-id-indexes'; +INSERT INTO alf_applied_patch + (id, description, fixes_from_schema, fixes_to_schema, applied_to_schema, target_schema, applied_on_date, applied_to_server, was_executed, succeeded, report) + VALUES + ( + 'patch.db-V4.0-Activiti-task-id-indexes', 'Manually executed script upgrade V4.0: Add missing Activiti indexes on task-id', + 0, 6003, -1, 6004, null, 'UNKNOWN', ${TRUE}, ${TRUE}, 'Script completed' + ); \ No newline at end of file diff --git a/config/alfresco/dbscripts/upgrade/4.0/org.hibernate.dialect.MySQLInnoDBDialect/ActivitiTaskIdIndexes.sql b/config/alfresco/dbscripts/upgrade/4.0/org.hibernate.dialect.MySQLInnoDBDialect/ActivitiTaskIdIndexes.sql new file mode 100644 index 0000000000..d9ead0f12a --- /dev/null +++ b/config/alfresco/dbscripts/upgrade/4.0/org.hibernate.dialect.MySQLInnoDBDialect/ActivitiTaskIdIndexes.sql @@ -0,0 +1,26 @@ +-- +-- Title: Add missing Activiti indexes on task-id for runtime and history variables. +-- Database: MySQL +-- Since: V4.0 Schema 5029 +-- Author: Frederik Heremans +-- +-- Please contact support@alfresco.com if you need assistance with the upgrade. +-- + +-- Add index to runtime variable table +create index ACT_IDX_VARIABLE_TASK_ID on ACT_RU_VARIABLE(TASK_ID_); + +-- Add index to history variable table +create index ACT_IDX_HI_DETAIL_TASK_ID on ACT_HI_DETAIL(TASK_ID_); + +-- +-- Record script finish +-- +DELETE FROM alf_applied_patch WHERE id = 'patch.db-V4.0-Activiti-task-id-indexes'; +INSERT INTO alf_applied_patch + (id, description, fixes_from_schema, fixes_to_schema, applied_to_schema, target_schema, applied_on_date, applied_to_server, was_executed, succeeded, report) + VALUES + ( + 'patch.db-V4.0-Activiti-task-id-indexes', 'Manually executed script upgrade V4.0: Add missing Activiti indexes on task-id', + 0, 6003, -1, 6004, null, 'UNKNOWN', ${TRUE}, ${TRUE}, 'Script completed' + ); \ No newline at end of file diff --git a/config/alfresco/dbscripts/upgrade/4.0/org.hibernate.dialect.PostgreSQLDialect/ActivitiTaskIdIndexes.sql b/config/alfresco/dbscripts/upgrade/4.0/org.hibernate.dialect.PostgreSQLDialect/ActivitiTaskIdIndexes.sql new file mode 100644 index 0000000000..beae52260b --- /dev/null +++ b/config/alfresco/dbscripts/upgrade/4.0/org.hibernate.dialect.PostgreSQLDialect/ActivitiTaskIdIndexes.sql @@ -0,0 +1,26 @@ +-- +-- Title: Add missing Activiti indexes on task-id for runtime and history variables. +-- Database: PostgreSQL +-- Since: V4.0 Schema 5029 +-- Author: Frederik Heremans +-- +-- Please contact support@alfresco.com if you need assistance with the upgrade. +-- + +-- Add index to runtime variable table +create index ACT_IDX_VARIABLE_TASK_ID on ACT_RU_VARIABLE(TASK_ID_); --(optional) + +-- Add index to history variable table +create index ACT_IDX_HI_DETAIL_TASK_ID on ACT_HI_DETAIL(TASK_ID_); --(optional) + +-- +-- Record script finish +-- +DELETE FROM alf_applied_patch WHERE id = 'patch.db-V4.0-Activiti-task-id-indexes'; +INSERT INTO alf_applied_patch + (id, description, fixes_from_schema, fixes_to_schema, applied_to_schema, target_schema, applied_on_date, applied_to_server, was_executed, succeeded, report) + VALUES + ( + 'patch.db-V4.0-Activiti-task-id-indexes', 'Manually executed script upgrade V4.0: Add missing Activiti indexes on task-id', + 0, 6003, -1, 6004, null, 'UNKNOWN', ${TRUE}, ${TRUE}, 'Script completed' + ); \ No newline at end of file diff --git a/config/alfresco/ehcache-default.xml b/config/alfresco/ehcache-default.xml index 7ba8664b29..a763850091 100644 --- a/config/alfresco/ehcache-default.xml +++ b/config/alfresco/ehcache-default.xml @@ -209,6 +209,13 @@ eternal="true" overflowToDisk="false" /> + + + + delete from + alf_transaction txn + where not exists + ( + select 1 + from + alf_node node + where + node.transaction_id = txn.id + ) + = #{minCommitTime}]]> + + + + + + + + + \ No newline at end of file diff --git a/config/alfresco/ibatis/org.hibernate.dialect.Dialect/permissions-common-SqlMap.xml b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/permissions-common-SqlMap.xml index ad07c5e162..e83c730e6c 100644 --- a/config/alfresco/ibatis/org.hibernate.dialect.Dialect/permissions-common-SqlMap.xml +++ b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/permissions-common-SqlMap.xml @@ -95,6 +95,10 @@ + + + + @@ -493,6 +497,26 @@ authority = ? + + + + diff --git a/config/alfresco/messages/action-config_fr.properties b/config/alfresco/messages/action-config_fr.properties index c84fd59481..cb19b659c2 100755 --- a/config/alfresco/messages/action-config_fr.properties +++ b/config/alfresco/messages/action-config_fr.properties @@ -156,7 +156,7 @@ export.title=Exporter l'espace export.description=Exporte un espace et, facultativement, ses enfants dans un progiciel d'exportation Alfresco. export.package.description=Package de contenu Alfresco pour l''espace ''{0}''. export.root.package.description=Package de contenu Alfresco pour l'entrep\u00f4t complet. -export.store.package.description=Export du magasin ''{0}'' de l''entrep\u00f4t Alfresco. +export.store.package.description=Export du store ''{0}'' de l''entrep\u00f4t Alfresco. export.generic.package.description=Export de l'entrep\u00f4t Alfresco. export.package.error=Fichier temporaire d'export non trouv\u00e9 @@ -187,26 +187,26 @@ simple-avm-submit.description=Transf\u00e8re tout nouveau noeud de l'\u00e9l\u00 simple-avm-promote.title=Simple transfert vers un bac \u00e0 sable simple-avm-promote.description=Transf\u00e8re tout nouveau noeud de l'\u00e9l\u00e9ment dans le bac \u00e0 sable s\u00e9lectionn\u00e9. -simple-avm-promote.target-store.display-label=Nom du magasin AVM cible. +simple-avm-promote.target-store.display-label=Nom du store AVM cible. -avm-revert-store.title=R\u00e9tablir un noeud unique dans un magasin. +avm-revert-store.title=R\u00e9tablir un noeud unique dans un store. avm-revert-store.description=R\u00e9tablit une version pr\u00e9c\u00e9dente du noeud indiqu\u00e9 et des noeuds inf\u00e9rieurs. avm-revert-store.version.display-label=Version \u00e0 r\u00e9tablir. -avm-revert-list.title=R\u00e9tablir une liste de noeuds dans un magasin. +avm-revert-list.title=R\u00e9tablir une liste de noeuds dans un store. avm-revert-list.description=R\u00e9tablit tous les noeuds de la liste. avm-revert-list.version.display-label=Version \u00e0 r\u00e9tablir. avm-revert-list.node-list.display-label=Cha\u00eene encod\u00e9e de la liste des noeuds \u00e0 r\u00e9tablir. avm-revert-list.flatten.display-label=S'il faut finaliser un environnement de recette apr\u00e8s le r\u00e9tablissement. -avm-revert-list.store.display-label=Nom du magasin r\u00e9tabli, uniquement n\u00e9cessaire pour la finalisation. -avm-revert-list.staging.display-label=Nom du magasin de recette pour la finalisation. -avm-revert-list.flatten-path.display-label=Chemin relatif du magasin qui doit \u00eatre finalis\u00e9. +avm-revert-list.store.display-label=Nom du store r\u00e9tabli, uniquement n\u00e9cessaire pour la finalisation. +avm-revert-list.staging.display-label=Nom du store de recette pour la finalisation. +avm-revert-list.flatten-path.display-label=Chemin relatif du store qui doit \u00eatre finalis\u00e9. avm-revert-to-version.title=R\u00e9tablir une version particuli\u00e8re d'un noeud. avm-revert-to-version.description=R\u00e9tablit une version donn\u00e9e d'un noeud. avm-revert-to-version.to-revert.display-label=Descripteur de noeud AVM de la version \u00e0 r\u00e9tablir. -avm-undo-list.title=Rendre une liste de noeuds dans un magasin transparente pour la recette. +avm-undo-list.title=Rendre une liste de noeuds dans un store transparente pour la recette. avm-undo-list.description=Retour arri\u00e8re dans le bac \u00e0 sable utilisateur. avm-undo-list.node-list.display-label=Cha\u00eene encod\u00e9e de la liste des noeuds \u00e0 r\u00e9tablir. diff --git a/config/alfresco/messages/bpm-messages_de.properties b/config/alfresco/messages/bpm-messages_de.properties index 9643358e2e..3f480292e8 100755 --- a/config/alfresco/messages/bpm-messages_de.properties +++ b/config/alfresco/messages/bpm-messages_de.properties @@ -1,13 +1,13 @@ -#Display labels for base Business Process Model +# Display labels for base Business Process Model bpm_businessprocessmodel.title=Modell f\u00fcr Gesch\u00e4ftsprozess bpm_businessprocessmodel.description=Grundlegende Definitionen aller Gesch\u00e4ftsprozesse -#Default transition +# Default transition bpm_businessprocessmodel.transition.title=Aufgabe erledigt bpm_businessprocessmodel.transition.description=Aufgabe erledigt -#Base Task +# Base Task bpm_businessprocessmodel.type.bpm_task.title=Aufgabe bpm_businessprocessmodel.type.bpm_task.description=Aufgabe bpm_businessprocessmodel.property.bpm_taskId.title=Identifikator @@ -29,7 +29,7 @@ bpm_businessprocessmodel.property.bpm_comment.description=Kommentar bpm_businessprocessmodel.association.bpm_pooledActors.title=Geb\u00fcndelte Benutzer bpm_businessprocessmodel.association.bpm_pooledActors.description=Pool -#Workflow Task +# Workflow Task bpm_businessprocessmodel.type.bpm_workflowTask.title=Aufgabe im Workflow bpm_businessprocessmodel.type.bpm_workflowTask.description=Vor einem Workflow zugewiesene Aufgabe bpm_businessprocessmodel.property.bpm_workflowDefinitionId.title=Definitions-ID des Workflows @@ -76,11 +76,11 @@ bpm_businessprocessmodel.association.bpm_groupAssignee.description=Bevollm\u00e4 bpm_businessprocessmodel.association.bpm_groupAssignees.title=Bevollm\u00e4chtigte f\u00fcr die Gruppe der Workflows bpm_businessprocessmodel.association.bpm_groupAssignees.description=Bevollm\u00e4chtigte f\u00fcr die Gruppe der Workflows -#Error Messages +# Error Messages workflow.get.task.definition.metadata.error=Aufgabentypdefinition {0} konnte nicht gefunden werden. workflow.package.already.associated.error=Dieser Node wird bereits als Workflow-Paket verwendet. NodeRef: {0} -#List constraint display labels +# List constraint display labels listconstraint.bpm_allowedPriority.1=Hoch listconstraint.bpm_allowedPriority.2=Mittel listconstraint.bpm_allowedPriority.3=Niedrig diff --git a/config/alfresco/messages/bpm-messages_es.properties b/config/alfresco/messages/bpm-messages_es.properties index a4fd57d01d..1e93c89844 100755 --- a/config/alfresco/messages/bpm-messages_es.properties +++ b/config/alfresco/messages/bpm-messages_es.properties @@ -1,13 +1,13 @@ -#Display labels for base Business Process Model +# Display labels for base Business Process Model bpm_businessprocessmodel.title=Modelo de procesos empresariales bpm_businessprocessmodel.description=Definiciones b\u00e1sicas de todos los procesos empresariales -#Default transition +# Default transition bpm_businessprocessmodel.transition.title=Tarea hecha bpm_businessprocessmodel.transition.description=Tarea hecha -#Base Task +# Base Task bpm_businessprocessmodel.type.bpm_task.title=Tarea bpm_businessprocessmodel.type.bpm_task.description=Tarea bpm_businessprocessmodel.property.bpm_taskId.title=Identificador @@ -29,7 +29,7 @@ bpm_businessprocessmodel.property.bpm_comment.description=Comentario bpm_businessprocessmodel.association.bpm_pooledActors.title=Usuarios agrupados bpm_businessprocessmodel.association.bpm_pooledActors.description=Grupo -#Workflow Task +# Workflow Task bpm_businessprocessmodel.type.bpm_workflowTask.title=Tarea de flujo de trabajo bpm_businessprocessmodel.type.bpm_workflowTask.description=Tarea asignada por un flujo de trabajo bpm_businessprocessmodel.property.bpm_workflowDefinitionId.title=Id de definici\u00f3n de flujo de trabajo @@ -76,11 +76,11 @@ bpm_businessprocessmodel.association.bpm_groupAssignee.description=Usuario a asi bpm_businessprocessmodel.association.bpm_groupAssignees.title=Usuarios a asignar la tarea del grupo de flujo de trabajo bpm_businessprocessmodel.association.bpm_groupAssignees.description=Usuarios a asignar la tarea del grupo de flujo de trabajo -#Error Messages +# Error Messages workflow.get.task.definition.metadata.error=No se pudo encontrar la definici\u00f3n de tipo de tarea {0}. workflow.package.already.associated.error=Este nodo ya se est\u00e1 utilizando como paquete de flujo de trabajo. NodeRef: {0} -#List constraint display labels +# List constraint display labels listconstraint.bpm_allowedPriority.1=Alta listconstraint.bpm_allowedPriority.2=Media listconstraint.bpm_allowedPriority.3=Baja diff --git a/config/alfresco/messages/bpm-messages_fr.properties b/config/alfresco/messages/bpm-messages_fr.properties index 00564a963a..787858e39c 100755 --- a/config/alfresco/messages/bpm-messages_fr.properties +++ b/config/alfresco/messages/bpm-messages_fr.properties @@ -1,13 +1,13 @@ -#Display labels for base Business Process Model +# Display labels for base Business Process Model bpm_businessprocessmodel.title=Mod\u00e8le de Processus M\u00e9tier bpm_businessprocessmodel.description=D\u00e9finitions de base de tous les Processus M\u00e9tier -#Default transition +# Default transition bpm_businessprocessmodel.transition.title=T\u00e2che termin\u00e9e bpm_businessprocessmodel.transition.description=T\u00e2che termin\u00e9e -#Base Task +# Base Task bpm_businessprocessmodel.type.bpm_task.title=T\u00e2che bpm_businessprocessmodel.type.bpm_task.description=T\u00e2che bpm_businessprocessmodel.property.bpm_taskId.title=Identifiant @@ -29,7 +29,7 @@ bpm_businessprocessmodel.property.bpm_comment.description=Commentaire bpm_businessprocessmodel.association.bpm_pooledActors.title=Liste bpm_businessprocessmodel.association.bpm_pooledActors.description=Liste -#Workflow Task +# Workflow Task bpm_businessprocessmodel.type.bpm_workflowTask.title=T\u00e2che du workflow bpm_businessprocessmodel.type.bpm_workflowTask.description=T\u00e2che assign\u00e9e par un Workflow bpm_businessprocessmodel.property.bpm_workflowDefinitionId.title=Identifiant de la d\u00e9finition du Workflow @@ -57,7 +57,6 @@ bpm_businessprocessmodel.aspect.bpm_workflowPackage.description=La collection du bpm_businessprocessmodel.type.bpm_activitiStartTask.title=T\u00e2che de d\u00e9marrage de workflow bpm_businessprocessmodel.type.bpm_activitiStartTask.description=T\u00e2che de r\u00e9colte des informations n\u00e9cessaires au d\u00e9marrage du workflow -#Workflow Start Task bpm_businessprocessmodel.type.bpm_startTask.title=T\u00e2che de D\u00e9marrage du Workflow bpm_businessprocessmodel.type.bpm_startTask.description=T\u00e2che utilis\u00e9e pour collecter des informations n\u00e9cessaires pour d\u00e9marrer le Workflow bpm_businessprocessmodel.property.bpm_workflowDescription.title=Description @@ -77,11 +76,11 @@ bpm_businessprocessmodel.association.bpm_groupAssignee.description=Propri\u00e9t bpm_businessprocessmodel.association.bpm_groupAssignees.title=Propri\u00e9taires du Groupe de Workflow bpm_businessprocessmodel.association.bpm_groupAssignees.description=Propri\u00e9taires du Groupe de Workflow -#Error Messages +# Error Messages workflow.get.task.definition.metadata.error=Impossible de trouver la d\u00e9finition de type de t\u00e2che {0}. workflow.package.already.associated.error=Ce n\u0153ud est d\u00e9j\u00e0 utilis\u00e9 comme paquetage de workflow. NodeRef : {0} -#List constraint display labels +# List constraint display labels listconstraint.bpm_allowedPriority.1=\u00c9lev\u00e9e listconstraint.bpm_allowedPriority.2=Moyenne listconstraint.bpm_allowedPriority.3=Basse diff --git a/config/alfresco/messages/bpm-messages_it.properties b/config/alfresco/messages/bpm-messages_it.properties index 9d39950619..6d7d3adbfb 100755 --- a/config/alfresco/messages/bpm-messages_it.properties +++ b/config/alfresco/messages/bpm-messages_it.properties @@ -1,13 +1,13 @@ -#Display labels for base Business Process Model +# Display labels for base Business Process Model bpm_businessprocessmodel.title=Modello di processo aziendale bpm_businessprocessmodel.description=Definizioni di base di tutti i processi aziendali -#Default transition +# Default transition bpm_businessprocessmodel.transition.title=Compito eseguito bpm_businessprocessmodel.transition.description=Compito eseguito -#Base Task +# Base Task bpm_businessprocessmodel.type.bpm_task.title=Compito bpm_businessprocessmodel.type.bpm_task.description=Compito bpm_businessprocessmodel.property.bpm_taskId.title=Identificativo @@ -29,7 +29,7 @@ bpm_businessprocessmodel.property.bpm_comment.description=Commento bpm_businessprocessmodel.association.bpm_pooledActors.title=Utenti nel pool bpm_businessprocessmodel.association.bpm_pooledActors.description=Pool -#Workflow Task +# Workflow Task bpm_businessprocessmodel.type.bpm_workflowTask.title=Compito del workflow bpm_businessprocessmodel.type.bpm_workflowTask.description=Compito assegnato da un workflow bpm_businessprocessmodel.property.bpm_workflowDefinitionId.title=ID di definizione del workflow @@ -76,11 +76,11 @@ bpm_businessprocessmodel.association.bpm_groupAssignee.description=Assegnatario bpm_businessprocessmodel.association.bpm_groupAssignees.title=Assegnatari del gruppo del workflow bpm_businessprocessmodel.association.bpm_groupAssignees.description=Assegnatari del gruppo del workflow -#Error Messages +# Error Messages workflow.get.task.definition.metadata.error=Impossibile trovare la definizione del tipo di compito {0}. workflow.package.already.associated.error=Questo nodo \u00e8 gi\u00e0 utilizzato come pacchetto di workflow! NodeRef: {0} -#List constraint display labels +# List constraint display labels listconstraint.bpm_allowedPriority.1=Alta listconstraint.bpm_allowedPriority.2=Media listconstraint.bpm_allowedPriority.3=Bassa diff --git a/config/alfresco/messages/bpm-messages_nl.properties b/config/alfresco/messages/bpm-messages_nl.properties index 97350d19fe..73b9ae891e 100755 --- a/config/alfresco/messages/bpm-messages_nl.properties +++ b/config/alfresco/messages/bpm-messages_nl.properties @@ -57,7 +57,6 @@ bpm_businessprocessmodel.aspect.bpm_workflowPackage.description=De verzameling c bpm_businessprocessmodel.type.bpm_activitiStartTask.title=Begintaak van de Werkstroom bpm_businessprocessmodel.type.bpm_activitiStartTask.description=Taak die wordt gebruikt om informatie te verzamelen om de werkstroom te initi\u00ebren -# Workflow Start Task bpm_businessprocessmodel.type.bpm_startTask.title=Begintaak van werkstroom bpm_businessprocessmodel.type.bpm_startTask.description=Taak die wordt gebruikt om informatie te verzamelen die nodig is om de werkstroom te starten bpm_businessprocessmodel.property.bpm_workflowDescription.title=Beschrijving diff --git a/config/alfresco/messages/content-model_de.properties b/config/alfresco/messages/content-model_de.properties index 8a2f079bc4..145bd2b4de 100755 --- a/config/alfresco/messages/content-model_de.properties +++ b/config/alfresco/messages/content-model_de.properties @@ -358,3 +358,6 @@ cm_contentmodel.property.cm_isIndexed.title=wurde in den Index aufgenommen cm_contentmodel.property.cm_isIndexed.description=ist der Node, der in den Index aufgenommen wurde und \u00fcber die Suche gefunden werden kann. cm_contentmodel.property.cm_isContentIndexed.title=ist Inhalt, der in den Index aufgenommen wurde cm_contentmodel.property.cm_isContentIndexed.description=Sind die d:content Eigenschaften des Nodes in den Index aufgenommen worden? + +cm_contentmodel.property.cm_tagScopeSummary.title=Tag Summary +cm_contentmodel.property.cm_tagScopeSummary.description=Tag Summary \ No newline at end of file diff --git a/config/alfresco/messages/content-model_es.properties b/config/alfresco/messages/content-model_es.properties index 4d68731499..207f618565 100755 --- a/config/alfresco/messages/content-model_es.properties +++ b/config/alfresco/messages/content-model_es.properties @@ -358,3 +358,6 @@ cm_contentmodel.property.cm_isIndexed.title=Indexar propiedades cm_contentmodel.property.cm_isIndexed.description=Es el nodo indexado y se puede encontrar mediante una b\u00fasqueda. cm_contentmodel.property.cm_isContentIndexed.title=Indexar contenido cm_contentmodel.property.cm_isContentIndexed.description=\u00bfEst\u00e1n las propiedades del nodo d:content indexadas? + +cm_contentmodel.property.cm_tagScopeSummary.title=Tag Summary +cm_contentmodel.property.cm_tagScopeSummary.description=Tag Summary \ No newline at end of file diff --git a/config/alfresco/messages/content-model_fr.properties b/config/alfresco/messages/content-model_fr.properties index c396771386..d84e80e7b0 100755 --- a/config/alfresco/messages/content-model_fr.properties +++ b/config/alfresco/messages/content-model_fr.properties @@ -358,3 +358,6 @@ cm_contentmodel.property.cm_isIndexed.title=Est index\u00e9 cm_contentmodel.property.cm_isIndexed.description=N\u0153ud index\u00e9 et pouvant \u00eatre faire l'objet d'une recherche. cm_contentmodel.property.cm_isContentIndexed.title=Est index\u00e9 selon le contenu cm_contentmodel.property.cm_isContentIndexed.description=Les propri\u00e9t\u00e9s d:content du n\u0153ud sont-elles index\u00e9es ? + +cm_contentmodel.property.cm_tagScopeSummary.title=Tag Summary +cm_contentmodel.property.cm_tagScopeSummary.description=Tag Summary \ No newline at end of file diff --git a/config/alfresco/messages/content-model_it.properties b/config/alfresco/messages/content-model_it.properties index b6898cd75b..da52f85316 100755 --- a/config/alfresco/messages/content-model_it.properties +++ b/config/alfresco/messages/content-model_it.properties @@ -358,3 +358,6 @@ cm_contentmodel.property.cm_isIndexed.title=Indicizzato cm_contentmodel.property.cm_isIndexed.description=Il nodo \u00e8 indicizzato e pu\u00f2 essere trovato tramite ricerca. cm_contentmodel.property.cm_isContentIndexed.title=Contenuto indicizzato cm_contentmodel.property.cm_isContentIndexed.description=Le propriet\u00e0 d:content del nodo sono indicizzate? + +cm_contentmodel.property.cm_tagScopeSummary.title=Tag Summary +cm_contentmodel.property.cm_tagScopeSummary.description=Tag Summary \ No newline at end of file diff --git a/config/alfresco/messages/content-model_ja.properties b/config/alfresco/messages/content-model_ja.properties index 4dd013b793..d31e31b9e8 100755 --- a/config/alfresco/messages/content-model_ja.properties +++ b/config/alfresco/messages/content-model_ja.properties @@ -358,3 +358,6 @@ cm_contentmodel.property.cm_isIndexed.title=\u5c5e\u6027\u5024\u30a4\u30f3\u30c7 cm_contentmodel.property.cm_isIndexed.description=\u691c\u7d22\u3067\u304d\u308b\u3088\u3046\u306b\u3001\u30ce\u30fc\u30c9\u306b\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u3092\u4ed8\u3051\u307e\u3059\u3002 cm_contentmodel.property.cm_isContentIndexed.title=\u30b3\u30f3\u30c6\u30f3\u30c4\u5168\u6587\u30a4\u30f3\u30c7\u30af\u30b7\u30f3\u30b0\u5bfe\u8c61 cm_contentmodel.property.cm_isContentIndexed.description=\u30ce\u30fc\u30c9\u306ed:content\u30d7\u30ed\u30d1\u30c6\u30a3\u306b\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u306f\u4ed8\u3051\u3089\u308c\u3066\u3044\u307e\u3059\u304b\uff1f + +cm_contentmodel.property.cm_tagScopeSummary.title=Tag Summary +cm_contentmodel.property.cm_tagScopeSummary.description=Tag Summary \ No newline at end of file diff --git a/config/alfresco/messages/content-model_nl.properties b/config/alfresco/messages/content-model_nl.properties index f871b2ebda..90a1c379ad 100755 --- a/config/alfresco/messages/content-model_nl.properties +++ b/config/alfresco/messages/content-model_nl.properties @@ -358,3 +358,6 @@ cm_contentmodel.property.cm_isIndexed.title=Is ge\u00efndexeerd cm_contentmodel.property.cm_isIndexed.description=De node is ge\u00efndexeerd en kan via de zoekfunctie worden gevonden. cm_contentmodel.property.cm_isContentIndexed.title=Is op content ge\u00efndexeerd cm_contentmodel.property.cm_isContentIndexed.description=De d:content-eigenschappen van de node zijn ge\u00efndexeerd. + +cm_contentmodel.property.cm_tagScopeSummary.title=Tag Summary +cm_contentmodel.property.cm_tagScopeSummary.description=Tag Summary \ No newline at end of file diff --git a/config/alfresco/messages/patch-service.properties b/config/alfresco/messages/patch-service.properties index babc3b866a..0c0c063e22 100644 --- a/config/alfresco/messages/patch-service.properties +++ b/config/alfresco/messages/patch-service.properties @@ -425,6 +425,8 @@ patch.activitiesTemplatesUpdate.description=Updates activities email templates. patch.activitiesTemplatesUpdate.error=Error retrieving base template when trying to patch the activity email templates. patch.activitiesTemplatesUpdate.result=Updated activities email templates. +patch.activitiesEmailTemplate.description=Creates activities email templates. + patch.avmToAdmRemoteStore.description=Migrates Share Surf config from AVM sitestore to DM Sites folder. patch.avmToAdmRemoteStore.complete=Completed Share Surf config migration. diff --git a/config/alfresco/messages/patch-service_de.properties b/config/alfresco/messages/patch-service_de.properties index e367a84b2f..4bcb96b00a 100755 --- a/config/alfresco/messages/patch-service_de.properties +++ b/config/alfresco/messages/patch-service_de.properties @@ -152,6 +152,7 @@ patch.siteLoadPatch.description=Loads a sample site into the repository. patch.siteLoadPatch.exists=The Site {0} already exists and so could not be imported patch.siteLoadPatch.noBootstrapViews=No bootstrap views were given for importing Site {0} - please check the bootstrap extension bean configuration patch.siteLoadPatch.result=Site {0} imported. +patch.siteLoadPatch.siteNotCreated=The site {0} is only created for new installs. patch.wcmFolders.description=Ensures the existance of the WCM specific 'Web Projects' and 'Web Forms' folders. patch.wcmFolders.webprojects.result.exists=The Web Projects folder already exists: {0} @@ -455,3 +456,8 @@ 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.avmIndexChildEntriesLower.description=Add an indexed column to avm_child_entries that allows case-insensitive querying of AVM files by name + +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 e367a84b2f..4bcb96b00a 100755 --- a/config/alfresco/messages/patch-service_es.properties +++ b/config/alfresco/messages/patch-service_es.properties @@ -152,6 +152,7 @@ patch.siteLoadPatch.description=Loads a sample site into the repository. patch.siteLoadPatch.exists=The Site {0} already exists and so could not be imported patch.siteLoadPatch.noBootstrapViews=No bootstrap views were given for importing Site {0} - please check the bootstrap extension bean configuration patch.siteLoadPatch.result=Site {0} imported. +patch.siteLoadPatch.siteNotCreated=The site {0} is only created for new installs. patch.wcmFolders.description=Ensures the existance of the WCM specific 'Web Projects' and 'Web Forms' folders. patch.wcmFolders.webprojects.result.exists=The Web Projects folder already exists: {0} @@ -455,3 +456,8 @@ 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.avmIndexChildEntriesLower.description=Add an indexed column to avm_child_entries that allows case-insensitive querying of AVM files by name + +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_fr.properties b/config/alfresco/messages/patch-service_fr.properties index e367a84b2f..4bcb96b00a 100755 --- a/config/alfresco/messages/patch-service_fr.properties +++ b/config/alfresco/messages/patch-service_fr.properties @@ -152,6 +152,7 @@ patch.siteLoadPatch.description=Loads a sample site into the repository. patch.siteLoadPatch.exists=The Site {0} already exists and so could not be imported patch.siteLoadPatch.noBootstrapViews=No bootstrap views were given for importing Site {0} - please check the bootstrap extension bean configuration patch.siteLoadPatch.result=Site {0} imported. +patch.siteLoadPatch.siteNotCreated=The site {0} is only created for new installs. patch.wcmFolders.description=Ensures the existance of the WCM specific 'Web Projects' and 'Web Forms' folders. patch.wcmFolders.webprojects.result.exists=The Web Projects folder already exists: {0} @@ -455,3 +456,8 @@ 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.avmIndexChildEntriesLower.description=Add an indexed column to avm_child_entries that allows case-insensitive querying of AVM files by name + +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_it.properties b/config/alfresco/messages/patch-service_it.properties index e367a84b2f..4bcb96b00a 100755 --- a/config/alfresco/messages/patch-service_it.properties +++ b/config/alfresco/messages/patch-service_it.properties @@ -152,6 +152,7 @@ patch.siteLoadPatch.description=Loads a sample site into the repository. patch.siteLoadPatch.exists=The Site {0} already exists and so could not be imported patch.siteLoadPatch.noBootstrapViews=No bootstrap views were given for importing Site {0} - please check the bootstrap extension bean configuration patch.siteLoadPatch.result=Site {0} imported. +patch.siteLoadPatch.siteNotCreated=The site {0} is only created for new installs. patch.wcmFolders.description=Ensures the existance of the WCM specific 'Web Projects' and 'Web Forms' folders. patch.wcmFolders.webprojects.result.exists=The Web Projects folder already exists: {0} @@ -455,3 +456,8 @@ 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.avmIndexChildEntriesLower.description=Add an indexed column to avm_child_entries that allows case-insensitive querying of AVM files by name + +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_ja.properties b/config/alfresco/messages/patch-service_ja.properties index e367a84b2f..4bcb96b00a 100755 --- a/config/alfresco/messages/patch-service_ja.properties +++ b/config/alfresco/messages/patch-service_ja.properties @@ -152,6 +152,7 @@ patch.siteLoadPatch.description=Loads a sample site into the repository. patch.siteLoadPatch.exists=The Site {0} already exists and so could not be imported patch.siteLoadPatch.noBootstrapViews=No bootstrap views were given for importing Site {0} - please check the bootstrap extension bean configuration patch.siteLoadPatch.result=Site {0} imported. +patch.siteLoadPatch.siteNotCreated=The site {0} is only created for new installs. patch.wcmFolders.description=Ensures the existance of the WCM specific 'Web Projects' and 'Web Forms' folders. patch.wcmFolders.webprojects.result.exists=The Web Projects folder already exists: {0} @@ -455,3 +456,8 @@ 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.avmIndexChildEntriesLower.description=Add an indexed column to avm_child_entries that allows case-insensitive querying of AVM files by name + +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_nl.properties b/config/alfresco/messages/patch-service_nl.properties index e367a84b2f..4bcb96b00a 100755 --- a/config/alfresco/messages/patch-service_nl.properties +++ b/config/alfresco/messages/patch-service_nl.properties @@ -152,6 +152,7 @@ patch.siteLoadPatch.description=Loads a sample site into the repository. patch.siteLoadPatch.exists=The Site {0} already exists and so could not be imported patch.siteLoadPatch.noBootstrapViews=No bootstrap views were given for importing Site {0} - please check the bootstrap extension bean configuration patch.siteLoadPatch.result=Site {0} imported. +patch.siteLoadPatch.siteNotCreated=The site {0} is only created for new installs. patch.wcmFolders.description=Ensures the existance of the WCM specific 'Web Projects' and 'Web Forms' folders. patch.wcmFolders.webprojects.result.exists=The Web Projects folder already exists: {0} @@ -455,3 +456,8 @@ 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.avmIndexChildEntriesLower.description=Add an indexed column to avm_child_entries that allows case-insensitive querying of AVM files by name + +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/site-model_de.properties b/config/alfresco/messages/site-model_de.properties new file mode 100755 index 0000000000..6fba171d12 --- /dev/null +++ b/config/alfresco/messages/site-model_de.properties @@ -0,0 +1,5 @@ +# Display labels for Site Model +st_siteModel.property.st_sitePreset.title=Site Voreinstellungen +st_siteModel.property.st_sitePreset.description=Site Voreinstellungen +st_siteModel.property.st_siteVisibility.title=Site Sichtbarkeit +st_siteModel.property.st_siteVisibility.description=Site Sichtbarkeit \ No newline at end of file diff --git a/config/alfresco/messages/site-model_es.properties b/config/alfresco/messages/site-model_es.properties new file mode 100755 index 0000000000..f390332084 --- /dev/null +++ b/config/alfresco/messages/site-model_es.properties @@ -0,0 +1,5 @@ +# Display labels for Site Model +st_siteModel.property.st_sitePreset.title=Plantilla de sitio +st_siteModel.property.st_sitePreset.description=Plantilla de sitio +st_siteModel.property.st_siteVisibility.title=Visibilidad del sitio +st_siteModel.property.st_siteVisibility.description=Visibilidad del sitio \ No newline at end of file diff --git a/config/alfresco/messages/site-model_fr.properties b/config/alfresco/messages/site-model_fr.properties new file mode 100755 index 0000000000..c460278cee --- /dev/null +++ b/config/alfresco/messages/site-model_fr.properties @@ -0,0 +1,5 @@ +# Display labels for Site Model +st_siteModel.property.st_sitePreset.title=Pr\u00e9configuration de site +st_siteModel.property.st_sitePreset.description=Pr\u00e9configuration de site +st_siteModel.property.st_siteVisibility.title=Visibilit\u00e9 du site +st_siteModel.property.st_siteVisibility.description=Visibilit\u00e9 du site \ No newline at end of file diff --git a/config/alfresco/messages/site-model_it.properties b/config/alfresco/messages/site-model_it.properties new file mode 100755 index 0000000000..4536dd618e --- /dev/null +++ b/config/alfresco/messages/site-model_it.properties @@ -0,0 +1,5 @@ +# Display labels for Site Model +st_siteModel.property.st_sitePreset.title=Modello di sito +st_siteModel.property.st_sitePreset.description=Modello di sito +st_siteModel.property.st_siteVisibility.title=Visibilit\u00e0 del sito +st_siteModel.property.st_siteVisibility.description=Visibilit\u00e0 del sito \ No newline at end of file diff --git a/config/alfresco/messages/site-model_ja.properties b/config/alfresco/messages/site-model_ja.properties new file mode 100755 index 0000000000..642eecd667 --- /dev/null +++ b/config/alfresco/messages/site-model_ja.properties @@ -0,0 +1,5 @@ +# Display labels for Site Model +st_siteModel.property.st_sitePreset.title=\u30b5\u30a4\u30c8\u306e\u4e8b\u524d\u8a2d\u5b9a +st_siteModel.property.st_sitePreset.description=\u30b5\u30a4\u30c8\u306e\u4e8b\u524d\u8a2d\u5b9a +st_siteModel.property.st_siteVisibility.title=\u30b5\u30a4\u30c8\u306e\u516c\u958b\u30ec\u30d9\u30eb +st_siteModel.property.st_siteVisibility.description=\u30b5\u30a4\u30c8\u306e\u516c\u958b\u30ec\u30d9\u30eb \ No newline at end of file diff --git a/config/alfresco/messages/templates-messages.properties b/config/alfresco/messages/templates-messages.properties index 91a58e7e2e..4c7e82d7d1 100644 --- a/config/alfresco/messages/templates-messages.properties +++ b/config/alfresco/messages/templates-messages.properties @@ -9,20 +9,9 @@ templates.show_audit.current_document_audit_info=Current Document Audit Info templates.show_audit.name=Name: templates.show_audit.user_name=User Name templates.show_audit.application=Application -templates.show_audit.service=Service templates.show_audit.method=Method templates.show_audit.timestamp=Timestamp templates.show_audit.values=Audit Entry Values -templates.show_audit.failed=Failed -templates.show_audit.message=Message -templates.show_audit.arg_1=Arg 1 -templates.show_audit.arg_2=Arg 2 -templates.show_audit.arg_3=Arg 3 -templates.show_audit.arg_4=Arg 4 -templates.show_audit.arg_5=Arg 5 -templates.show_audit.return=Return -templates.show_audit.thowable=Throwable -templates.show_audit.tx=TX templates.show_audit.current_space_audit_info=Current Space Audit Info: #recent_docs.ftl diff --git a/config/alfresco/messages/wdr-messages_fr.properties b/config/alfresco/messages/wdr-messages_fr.properties index ca9cc9aec4..199e97f0db 100755 --- a/config/alfresco/messages/wdr-messages_fr.properties +++ b/config/alfresco/messages/wdr-messages_fr.properties @@ -7,6 +7,6 @@ wdr.err.unable_prepare_missing_file=Pr\u00e9paration impossible, fichier tempora wdr.err.invalid_ticket=D\u00e9ploiement expir\u00e9 ou ticket non valide. {0} wdr.err.unable_commit=Validation impossible -wdr.avm.snapshot_tag=D\u00e9ploiement depuis le magasin : {0}, version : {1} -wdr.avm.snapshot_description=D\u00e9ploiement depuis le magasin : {0}, version : {1} +wdr.avm.snapshot_tag=D\u00e9ploiement depuis le store : {0}, version : {1} +wdr.avm.snapshot_description=D\u00e9ploiement depuis le store : {0}, version : {1} diff --git a/config/alfresco/node-services-context.xml b/config/alfresco/node-services-context.xml index 83df8e0955..b9c7d6acc2 100644 --- a/config/alfresco/node-services-context.xml +++ b/config/alfresco/node-services-context.xml @@ -207,6 +207,9 @@ ${index.tracking.minRecordPurgeAgeDays} + + ${index.tracking.purgeSize} + diff --git a/config/alfresco/patch/patch-services-context.xml b/config/alfresco/patch/patch-services-context.xml index 61668d4f23..2a8a803cb9 100644 --- a/config/alfresco/patch/patch-services-context.xml +++ b/config/alfresco/patch/patch-services-context.xml @@ -3080,5 +3080,16 @@ classpath:alfresco/dbscripts/upgrade/3.4/${db.script.dialect}/AVM-index-child-entries-lower.sql + + + + + + + + + classpath:alfresco/dbscripts/upgrade/4.0/${db.script.dialect}/ActivitiTaskIdIndexes.sql + + diff --git a/config/alfresco/public-services-security-context.xml b/config/alfresco/public-services-security-context.xml index f8c3a9089a..0ff8669192 100644 --- a/config/alfresco/public-services-security-context.xml +++ b/config/alfresco/public-services-security-context.xml @@ -83,6 +83,9 @@ + + + @@ -92,6 +95,9 @@ + + ${security.anyDenyDenies} + @@ -314,9 +320,9 @@ - + - + @@ -372,8 +378,8 @@ org.alfresco.service.cmr.repository.NodeService.getAspects=ACL_NODE.0.sys:base.ReadProperties org.alfresco.service.cmr.repository.NodeService.deleteNode=ACL_NODE.0.sys:base.DeleteNode org.alfresco.service.cmr.repository.NodeService.addChild=ACL_NODE.0.sys:base.CreateChildren,ACL_NODE.1.sys:base.ReadProperties - org.alfresco.service.cmr.repository.NodeService.removeChild=ACL_NODE.0.sys:base.DeleteChildren,ACL_NODE.1.sys:base.DeleteNode - org.alfresco.service.cmr.repository.NodeService.removeChildAssociation=ACL_PARENT.0.sys:base.DeleteChildren,ACL_NODE.0.sys:base.DeleteNode + org.alfresco.service.cmr.repository.NodeService.removeChild=ACL_NODE.0.sys:base.DeleteChildren,ACL_PRI_CHILD_ASSOC_ON_CHILD.0.1.sys:base.DeleteNode + org.alfresco.service.cmr.repository.NodeService.removeChildAssociation=ACL_PARENT.0.sys:base.DeleteChildren,ACL_PRI_CHILD_ASSOC_ON_CHILD.0.sys:base.DeleteNode org.alfresco.service.cmr.repository.NodeService.getProperties=ACL_NODE.0.sys:base.ReadProperties org.alfresco.service.cmr.repository.NodeService.getProperty=ACL_NODE.0.sys:base.ReadProperties org.alfresco.service.cmr.repository.NodeService.setProperties=ACL_NODE.0.sys:base.WriteProperties diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties index e786b4761b..b9018bd2d5 100644 --- a/config/alfresco/repository.properties +++ b/config/alfresco/repository.properties @@ -100,6 +100,9 @@ index.tracking.disableInTransactionIndexing=false # with a more recent backup of the Lucene indexes or the indexes will have to be fully rebuilt. # Use -1 to disable purging. This can be switched on at any stage. index.tracking.minRecordPurgeAgeDays=30 +# Unused transactions will be purged in chunks determined by commit time boundaries. 'index.tracking.purgeSize' specifies the size +# of the chunk (in ms). Default is a couple of hours. +index.tracking.purgeSize=7200000 # Reindexing of missing content is by default 'never' carried out. # The cron expression below can be changed to control the timing of this reindexing. @@ -378,7 +381,7 @@ db.pool.abandoned.log=false audit.enabled=true audit.tagging.enabled=true audit.alfresco-access.enabled=false -audit.alfresco-access.sub-events.enabled=false +audit.alfresco-access.sub-actions.enabled=false audit.cmischangelog.enabled=false audit.dod5015.enabled=false # Setting this flag to true will force startup failure when invalid audit configurations are detected @@ -566,6 +569,7 @@ img.root=./ImageMagick img.dyn=${img.root}/lib img.exe=${img.root}/bin/convert swf.exe=./bin/pdf2swf +swf.languagedir=. # Thumbnail Service system.thumbnail.generate=true @@ -645,12 +649,12 @@ V2.1-A.fixes.to.schema=0 authentication.chain=alfrescoNtlm1:alfrescoNtlm # Do authentication tickets expire or live for ever? -authentication.ticket.ticketsExpire=false +authentication.ticket.ticketsExpire=true # If ticketsEpire is true then how they should expire? # Valid values are: AFTER_INACTIVITY, AFTER_FIXED_TIME, DO_NOT_EXPIRE # The default is AFTER_FIXED_TIME -authentication.ticket.expiryMode=AFTER_FIXED_TIME +authentication.ticket.expiryMode=AFTER_INACTIVITY # If authentication.ticket.ticketsExpire is true and # authentication.ticket.expiryMode is AFTER_FIXED_TIME or AFTER_INACTIVITY, @@ -797,6 +801,10 @@ deployment.filesystem.default.metadatadir=${deployment.filesystem.metadatadir}/d orphanReaper.lockRefreshTime=60000 orphanReaper.lockTimeOut=3600000 + +# security +security.anyDenyDenies=true + # # Encryption properties # @@ -904,4 +912,7 @@ system.content.caching.maxFileSizeMB=0 mybatis.useLocalCaches=false -fileFolderService.checkHidden.enabled=true \ No newline at end of file +fileFolderService.checkHidden.enabled=true + + +ticket.cleanup.cronExpression=0 0 * * * ? \ No newline at end of file diff --git a/config/alfresco/scheduled-jobs-context.xml b/config/alfresco/scheduled-jobs-context.xml index e8a543d93d..eafb4b93f6 100644 --- a/config/alfresco/scheduled-jobs-context.xml +++ b/config/alfresco/scheduled-jobs-context.xml @@ -273,4 +273,31 @@ + + + + + org.alfresco.repo.security.authentication.TicketCleanupJob + + + + + + + + + + + + + + + + + + + ${ticket.cleanup.cronExpression} + + + diff --git a/config/alfresco/subsystems/fileServers/default/network-protocol-context.xml b/config/alfresco/subsystems/fileServers/default/network-protocol-context.xml index 36577b79a3..35c240a145 100644 --- a/config/alfresco/subsystems/fileServers/default/network-protocol-context.xml +++ b/config/alfresco/subsystems/fileServers/default/network-protocol-context.xml @@ -146,26 +146,38 @@ - ^\._.* - 30000 + ^\._.txt* + 60000 HIGH [0-9A-F]{8}+$ - 30000 + 60000 HIGH ~WRD.*.TMP - 30000 + 60000 HIGH [0-9A-F]*.TMP$ - 30000 + 60000 + HIGH + + + + .*D_[0-9]*.TMP$ + 60000 + HIGH + + + + ^[^\._].*[0-9].pptx$ + 60000 HIGH diff --git a/config/alfresco/subsystems/thirdparty/default/swf-transform-context.xml b/config/alfresco/subsystems/thirdparty/default/swf-transform-context.xml index a709a87530..9a1fa252f9 100644 --- a/config/alfresco/subsystems/thirdparty/default/swf-transform-context.xml +++ b/config/alfresco/subsystems/thirdparty/default/swf-transform-context.xml @@ -26,7 +26,7 @@ - ${swf.exe} -T ${flashVersion} ${swf.encoder.params} ${source} -o ${target} + ${swf.exe} -T ${flashVersion} ${swf.encoder.params} ${source} -o ${target} -s languagedir=${swf.languagedir} diff --git a/config/alfresco/version.properties b/config/alfresco/version.properties index d0eeed8784..a9aa890379 100644 --- a/config/alfresco/version.properties +++ b/config/alfresco/version.properties @@ -19,4 +19,4 @@ version.build=@build-number@ # Schema number -version.schema=6003 +version.schema=6004 diff --git a/source/java/org/alfresco/filesys/auth/cifs/CifsAuthenticatorBase.java b/source/java/org/alfresco/filesys/auth/cifs/CifsAuthenticatorBase.java index 0e1c624205..02ccbb1208 100644 --- a/source/java/org/alfresco/filesys/auth/cifs/CifsAuthenticatorBase.java +++ b/source/java/org/alfresco/filesys/auth/cifs/CifsAuthenticatorBase.java @@ -31,6 +31,7 @@ import org.alfresco.jlan.server.filesys.DiskDeviceContext; import org.alfresco.jlan.server.filesys.DiskInterface; import org.alfresco.jlan.server.filesys.DiskSharedDevice; import org.alfresco.jlan.server.filesys.SrvDiskInfo; +import org.alfresco.jlan.smb.server.SMBSrvException; import org.alfresco.model.ContentModel; import org.alfresco.repo.management.subsystems.ActivateableBean; import org.alfresco.repo.security.authentication.AuthenticationComponent; @@ -64,8 +65,7 @@ public abstract class CifsAuthenticatorBase extends CifsAuthenticator implements { // Logging - /** The Constant logger. */ - protected static final Log logger = LogFactory.getLog("org.alfresco.smb.protocol.auth"); + protected static final Log logger = LogFactory.getLog(CifsAuthenticatorBase.class); // MD4 hash decoder @@ -342,7 +342,7 @@ public abstract class CifsAuthenticatorBase extends CifsAuthenticator implements * @param client * ClientInfo */ - protected final void getHomeFolderForUser(final ClientInfo client) + protected final void getHomeFolderForUser(final ClientInfo client) { // Check if the client is an Alfresco client, and not a null logon @@ -356,7 +356,7 @@ public abstract class CifsAuthenticatorBase extends CifsAuthenticator implements doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() { - public Object execute() throws Throwable + public Object execute() throws SMBSrvException { NodeRef homeSpaceRef = (NodeRef) getNodeService().getProperty( getPersonService().getPerson(client.getUserName()), ContentModel.PROP_HOMEFOLDER); @@ -364,17 +364,25 @@ public abstract class CifsAuthenticatorBase extends CifsAuthenticator implements return null; } }); - } + } /** * Map the case insensitive logon name to the internal person object user name. + * And optionally check whether the user is enabled. + * * * @param userName * String - * @return String + * @param checkEnabled + * @return the user name */ - protected final String mapUserNameToPerson(final String userName) + public final String mapUserNameToPerson(final String userName, final boolean checkEnabled) { + if(logger.isDebugEnabled()) + { + logger.debug("mapUserNameToPerson userName:" + userName + ", checkEnabled:" + checkEnabled); + } + // Do the lookup as the system user return AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() { @@ -385,8 +393,6 @@ public abstract class CifsAuthenticatorBase extends CifsAuthenticator implements public String execute() throws Throwable { - // Get the home folder for the user - String personName = getPersonService().getUserIdentifier(userName); // Check if the person exists @@ -396,9 +402,32 @@ public abstract class CifsAuthenticatorBase extends CifsAuthenticator implements // Force creation of a person if possible getPersonService().getPerson(userName); personName = getPersonService().getUserIdentifier(userName); - return personName == null ? userName : personName; } - return personName; + + if(checkEnabled && personName != null) + { + /** + * Check the authenticator is enabled + */ + boolean isAuthenticationEnabled = getAuthenticationService().getAuthenticationEnabled(personName); + if(!isAuthenticationEnabled) + { + logger.debug("autentication service says user is not enabled"); + throw new AuthenticationException("Authentication not enabled for:" + userName); + } + + /** + * Check the person is enabled + */ + boolean isEnabled = personService.isEnabled(personName); + if(!isEnabled) + { + logger.debug("person service says user is not enabled"); + throw new AuthenticationException("Authentication not enabled for person:" + userName); + } + } + + return personName == null ? userName : personName; } }); } @@ -410,8 +439,8 @@ public abstract class CifsAuthenticatorBase extends CifsAuthenticator implements * * @param client ClientInfo or null to clear the context */ - public void setCurrentUser(final ClientInfo client) { - + public void setCurrentUser(final ClientInfo client) + { // Check the account type and setup the authentication context // No need for a transaction to clear the context @@ -541,7 +570,7 @@ public abstract class CifsAuthenticatorBase extends CifsAuthenticator implements * @param cInfo * ClientInfo */ - protected final void checkForAdminUserName(final ClientInfo cInfo) + protected final void checkForAdminUserName(final ClientInfo cInfo) { // Check if the user name is an administrator @@ -549,7 +578,7 @@ public abstract class CifsAuthenticatorBase extends CifsAuthenticator implements doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() { - public Object execute() throws Throwable + public Object execute() { if (cInfo.getLogonType() == ClientInfo.LogonNormal && getAuthorityService().isAdminAuthority(cInfo.getUserName())) diff --git a/source/java/org/alfresco/filesys/auth/cifs/EnterpriseCifsAuthenticator.java b/source/java/org/alfresco/filesys/auth/cifs/EnterpriseCifsAuthenticator.java index f171737737..ef2d68dda4 100644 --- a/source/java/org/alfresco/filesys/auth/cifs/EnterpriseCifsAuthenticator.java +++ b/source/java/org/alfresco/filesys/auth/cifs/EnterpriseCifsAuthenticator.java @@ -38,7 +38,6 @@ import javax.security.sasl.RealmCallback; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.filesys.alfresco.AlfrescoClientInfo; -import org.alfresco.jlan.debug.Debug; import org.alfresco.jlan.server.auth.AuthenticatorException; import org.alfresco.jlan.server.auth.ClientInfo; import org.alfresco.jlan.server.auth.NTLanManAuthContext; @@ -73,6 +72,8 @@ import org.alfresco.repo.security.authentication.AuthenticationException; import org.alfresco.repo.security.authentication.NTLMMode; import org.alfresco.repo.security.authentication.ntlm.NLTMAuthenticator; import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.ietf.jgss.Oid; import org.springframework.extensions.config.ConfigElement; @@ -88,6 +89,9 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Constants // // Default login configuration entry name + protected static final Log logger = LogFactory.getLog(EnterpriseCifsAuthenticator.class); + + // logger is defined in base class. private static final String LoginConfigEntry = "AlfrescoCIFS"; @@ -221,7 +225,9 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Debug if ( logger.isInfoEnabled() && enaTktCracking) + { logger.info("CIFS Kerberos authentication, ticket cracking enabled (for mutual authentication)"); + } } /** @@ -251,8 +257,10 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement setPassword(srvPassword.getValue()); } else + { throw new InvalidConfigurationException("CIFS service account password not specified"); - + } + // Get the login configuration entry name ConfigElement loginEntry = params.getChild("LoginEntry"); @@ -265,7 +273,9 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement setJaasConfigEntryName(loginEntry.getValue()); } else + { throw new InvalidConfigurationException("Invalid login entry specified"); + } } setDisableNTLM(params.getChild("disableNTLM") != null); @@ -338,7 +348,9 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Debug if (logger.isErrorEnabled()) + { logger.error("CIFS Kerberos authenticator error", ex); + } throw new InvalidConfigurationException("Failed to login CIFS server service"); } @@ -350,10 +362,10 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement m_accountName = princ.getName(); - // DEBUG - if (logger.isDebugEnabled()) + { logger.debug("Logged on using principal " + m_accountName); + } // Create the Oid list for the SPNEGO NegTokenInit, include NTLMSSP for fallback @@ -363,9 +375,7 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement if (logger.isDebugEnabled()) { - logger.debug("Enabling mechTypes :-"); - logger.debug(" Kerberos5"); - logger.debug(" MS-Kerberos5"); + logger.debug("Enabling mechTypes :-Kerberos5 MS-Kerberos5"); } // Always enable Kerberos @@ -380,7 +390,9 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // DEBUG if (logger.isDebugEnabled()) - logger.debug(" NTLMSSP"); + { + logger.debug(" Enabling NTLMSSP"); + } } // Indicate that SPNEGO security blobs are being used @@ -409,14 +421,13 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement if (!isKerberosEnabled() && (!(getAuthenticationComponent() instanceof NLTMAuthenticator) || getNTLMAuthenticator().getNTLMMode() != NTLMMode.MD4_PROVIDER)) { - // Log an error - - logger.error("No valid CIFS authentication combination available"); - logger.error("Either enable Kerberos support or use an SSO-enabled authentication component that supports MD4 hashed passwords"); - + if(logger.isDebugEnabled()) + { + logger.debug("No valid CIFS authentication combination available, Either enable Kerberos support or use an SSO-enabled authentication component that supports MD4 hashed passwords"); + } // Throw an exception to stop the CIFS server startup - throw new AlfrescoRuntimeException("Invalid CIFS authenticator configuration"); + throw new AlfrescoRuntimeException("No valid CIFS authentication combination available, Either enable Kerberos support or use an SSO-enabled authentication component that supports MD4 hashed passwords"); } } @@ -488,8 +499,9 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Debug if (logger.isErrorEnabled()) - logger.error("Error creating SPNEGO NegTokenInit blob", ex); - + { + logger.error("Unable to create SPNEGO NegTokenInit blob", ex); + } throw new AuthenticatorException("Failed to create SPNEGO NegTokenInit blob"); } @@ -644,49 +656,97 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement } /** - * Process the CIFS session setup request packet and build the session setup response - * + * Process the CIFS session setup request packet and build the session setup response. + *

+ * This is the boundary between alfresco and JLAN. So is responsible for logging and + * ensuring that the exceptions are correct for JLAN. + *

* @param sess SMBSrvSession * @param reqPkt SMBSrvPacket * @exception SMBSrvException */ public void processSessionSetup(final SMBSrvSession sess, final SMBSrvPacket reqPkt) + throws SMBSrvException + { + try + { + processAlfrescoSessionSetup(sess, reqPkt); + } + catch (SMBSrvException e) + { + /* + * A JLAN SMBSrvException is not human readable so we need to log the + * error before throwing, rather than logging the error here. + */ + if(logger.isDebugEnabled()) + { + logger.debug("Returning SMBSrvException to JLAN", e); + } + throw e; + } + catch (AlfrescoRuntimeException a) + { + Throwable c = a.getCause(); + + if(c != null) + { + if(a.getCause() instanceof SMBSrvException) + { + logger.error(c.getMessage(), c); + throw (SMBSrvException)c; + } + } + logger.error(a.getMessage(), a); + throw new SMBSrvException( SMBStatus.NTAccessDenied, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); + + } + catch (Throwable t) + { + logger.error(t.getMessage(), t); + throw new SMBSrvException( SMBStatus.NTAccessDenied, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); + } + } + + /** + * Internal setup method for alfresco. There's lots of CIFS specific stuff here that should + * be in JLAN. + * + * @param sess the JLAN session. + * @param reqPkt the CIFS request packet + * @throws SMBSrvException + */ + private void processAlfrescoSessionSetup(final SMBSrvSession sess, final SMBSrvPacket reqPkt) throws SMBSrvException { + logger.debug("Start process Alfresco Session Setup"); + // Check that the received packet looks like a valid NT session setup andX request - if (reqPkt.checkPacketIsValid(12, 0) == false) - throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv); - + { + if(logger.isErrorEnabled()) + { + logger.error("Invalid packet received, return SMBStatus.NTInvalidParameter"); + } + throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.ErrSrv, SMBStatus.SRVNonSpecificError); + } // Check if the request is using security blobs or the older hashed password format if ( reqPkt.getParameterCount() == 13) { - try + logger.debug("parameter count == 13, do Hashed Password Logon"); + + doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() { - // Start a transaction - - doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() - { - - public Object execute() throws Throwable - { - // Process the hashed password session setup - - doHashedPasswordLogon(sess, reqPkt); - return null; - } - }); - } - catch ( Exception ex) - { - // Convert to an access denied exception - - throw new SMBSrvException( SMBStatus.NTAccessDenied, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); - } + public Object execute() throws SMBSrvException + { + // Process the hashed password session setup + logger.debug("about to call doHashedPasswordLogon"); + doHashedPasswordLogon(sess, reqPkt); + return null; + } + }); // Hashed password processing complete - return; } @@ -718,35 +778,40 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement String domain = ""; - if (reqPkt.hasMoreData()) { - + if (reqPkt.hasMoreData()) + { // Extract the callers domain name - domain = reqPkt.unpackString(isUni); if (domain == null) - throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv); + { + logger.error("domain is null, return SMBStatus.NTInvalidParameter"); + throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.ErrSrv, SMBStatus.SRVNonSpecificError); + } } // Extract the clients native operating system String clientOS = ""; - if (reqPkt.hasMoreData()) { + if (reqPkt.hasMoreData()) + { // Extract the callers operating system name clientOS = reqPkt.unpackString(isUni); if (clientOS == null) - throw new SMBSrvException( SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv); + { + logger.error("clientOS is null, return SMBStatus.NTInvalidParameter"); + throw new SMBSrvException( SMBStatus.NTInvalidParameter, SMBStatus.ErrSrv, SMBStatus.SRVNonSpecificError); + } } - // DEBUG - if (logger.isDebugEnabled()) - logger.debug("NT Session setup " + (useRawNTLMSSP() ? "NTLMSSP" : "SPNEGO") + ", MID=" + reqPkt.getMultiplexId() + ", UID=" + reqPkt.getUserId() + ", PID=" + reqPkt.getProcessId()); - + { + logger.debug("NT Session setup " + (useRawNTLMSSP() ? "NTLMSSP" : "SPNEGO") + ", MID=" + reqPkt.getMultiplexId() + ", UID=" + reqPkt.getUserId() + ", PID=" + reqPkt.getProcessId()); + } // Store the client maximum buffer size, maximum multiplexed requests count and client capability flags sess.setClientMaximumBufferSize(maxBufSize != 0 ? maxBufSize : SMBSrvSession.DefaultBufferSize); @@ -764,7 +829,9 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Set the remote address, if available if ( sess.hasRemoteAddress()) - client.setClientAddress(sess.getRemoteAddress().getHostAddress()); + { + client.setClientAddress(sess.getRemoteAddress().getHostAddress()); + } // Set the process id for this client, for multi-stage logons @@ -780,27 +847,28 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement final boolean isNTLMSSP; try - { - + { // Check if the blob has the NTLMSSP signature - - if ( secBlobLen >= NTLM.Signature.length) { - + if ( secBlobLen >= NTLM.Signature.length) + { // Check for the NTLMSSP signature - int idx = 0; while ( idx < NTLM.Signature.length && buf[secBlobPos + idx] == NTLM.Signature[ idx]) + { idx++; + } isNTLMSSP = ( idx == NTLM.Signature.length); } - else { + else + { isNTLMSSP = false; } - - // Start a transaction - respBlob = doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + /** + * Extracted parameters from CIFS, Now try and do the logon + */ + respBlob = doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() { public byte[] execute() throws Throwable @@ -820,15 +888,17 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement return doSpnegoSessionSetup(sess, client, buf, secBlobPos, secBlobLen, isUni); } } - }); - + }); } catch ( Exception ex) { - // Cleanup any stored context - sess.removeSetupObject( client.getProcessId()); + if( ex instanceof SMBSrvException) + { + throw (SMBSrvException)ex; + } + // Convert to an access denied exception if necessary if (ex instanceof AlfrescoRuntimeException && ex.getCause() instanceof SMBSrvException) @@ -837,15 +907,20 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement } else { + logger.error("Access denied", ex); throw new SMBSrvException( SMBStatus.NTAccessDenied, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } - // Debug + /* + * We have logged on - so set up the response + */ - if ( logger.isDebugEnabled() && sess.hasDebug(SMBSrvSession.DBG_NEGOTIATE)) + if ( logger.isDebugEnabled()) + { logger.debug("User " + client.getUserName() + " logged on " + (client != null ? " (type " + client.getLogonTypeString() + ")" : "")); - + } + // Update the client information if not already set if ( sess.getClientInformation() == null || @@ -893,23 +968,24 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement if ( reqLen > respPkt.getAvailableLength()) { - try { + try + { // Allocate a new buffer for the response respPkt = sess.getPacketPool().allocatePacket(respPkt.getByteOffset() + reqLen, reqPkt); } - catch (NoPooledMemoryException ex) { - - // DEBUG - - if ( Debug.EnableDbg && hasDebug()) - Debug.println("Authenticator failed to allocate packet from pool, reqSiz=" - + (respPkt.getByteOffset() + respLen)); + catch (NoPooledMemoryException ex) + { + if(logger.isErrorEnabled()) + { + logger.error("Authenticator failed to allocate packet from pool, reqSiz=" + + (respPkt.getByteOffset() + respLen)); + } // Return a server error to the client - throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.SRVNoBuffers, SMBStatus.ErrSrv); + throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrSrv, SMBStatus.SRVNoBuffers); } } @@ -972,19 +1048,13 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement if ( uid == VirtualCircuit.InvalidUID) { - // DEBUG - - if ( logger.isDebugEnabled() && sess.hasDebug( SMBSrvSession.DBG_NEGOTIATE)) - logger.debug("Failed to allocate UID for virtual circuit, " + vc); - - // Failed to allocate a UID - - throw new SMBSrvException(SMBStatus.NTTooManySessions, SMBStatus.SRVTooManyUIDs, SMBStatus.ErrSrv); + logger.error("Failed to allocate UID for virtual circuit, " + vc); + // Failed to allocate a UID + throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrSrv, SMBStatus.SRVTooManyUIDs); + } - else if ( logger.isDebugEnabled() && sess.hasDebug( SMBSrvSession.DBG_NEGOTIATE)) { - - // DEBUG - + else if ( logger.isDebugEnabled()) + { logger.debug("Allocated UID=" + uid + " for VC=" + vc); } } @@ -1046,6 +1116,7 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement byte[] secbuf, int secpos, int seclen, boolean unicode) throws SMBSrvException { // Determine the NTLmSSP message type + logger.debug("Start doNTLmsspSessionSetup"); int msgType = NTLMMessage.isNTLMType( secbuf, secpos); byte[] respBlob = null; @@ -1054,21 +1125,25 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement { // DEBUG - if ( logger.isDebugEnabled()) + if ( logger.isErrorEnabled()) { - logger.debug("Invalid NTLMSSP token received"); - logger.debug(" Token=" + HexDump.hexString( secbuf, secpos, seclen, " ")); + logger.error("Invalid NTLMSSP token received, Token= " + HexDump.hexString( secbuf, secpos, seclen, " ")); } // Return a logon failure status - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } // Check for a type 1 NTLMSSP message else if ( msgType == NTLM.Type1) { + if ( logger.isDebugEnabled()) + { + logger.debug("NTLMsspSessionSetup Type1"); + } + // Create the type 1 NTLM message from the token Type1NTLMMessage type1Msg = new Type1NTLMMessage( secbuf, secpos, seclen); @@ -1117,6 +1192,10 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement } else if ( msgType == NTLM.Type3) { + if ( logger.isDebugEnabled()) + { + logger.debug("NTLmsspSessionSetup Type3"); + } // Create the type 3 NTLM message from the token Type3NTLMMessage type3Msg = new Type3NTLMMessage( secbuf, secpos, seclen, unicode); @@ -1130,8 +1209,9 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement sess.removeSetupObject( client.getProcessId()); // Return a logon failure + logger.error("NTLMSSP Logon failure - type 2 message not found"); - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } // Determine if the client sent us NTLMv1 or NTLMv2 @@ -1149,7 +1229,9 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Debug if ( logger.isDebugEnabled()) + { logger.debug("Logged on using NTLMSSP/NTLMv2"); + } } else { @@ -1160,7 +1242,9 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Debug if ( logger.isDebugEnabled()) + { logger.debug("Logged on using NTLMSSP/NTLMv2SessKey"); + } } } else @@ -1172,7 +1256,9 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Debug if ( logger.isDebugEnabled()) + { logger.debug("Logged on using NTLMSSP/NTLMv1"); + } } } @@ -1228,11 +1314,11 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement { // Log the error - logger.error(ex); + logger.error("I/O Error with SPNEGO authentication", ex); // Return a logon failure status - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } // Get the second stage NTLMSSP blob @@ -1270,11 +1356,11 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement { // Log the error - logger.error(ex); + logger.error("Unable to decode the SPNEGO token", ex); // Return a logon failure status - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } // Determine the authentication mechanism the client is using and logon @@ -1321,8 +1407,9 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement } // No valid authentication mechanism + logger.error("No authentication mechanism for SPNEGO found"); - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } else @@ -1333,7 +1420,7 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Return a logon failure status - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } // Generate the NegTokenTarg blob @@ -1348,14 +1435,9 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement } catch ( IOException ex) { - // Debug + logger.error("SPNEGO unable to encode NegTokenTarg", ex); - if ( logger.isDebugEnabled()) - logger.debug("Failed to encode NegTokenTarg", ex); - - // Failed to build response blob - - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } // Return the SPNEGO response blob @@ -1416,8 +1498,8 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement { // Failed to parse AP-REQ - if ( logger.isDebugEnabled()) - logger.debug("Failed to parse AP-REQ, " + ex.toString()); + + logger.error("Kerberos Failed to parse AP-REQ ", ex); // Return a logon failure status @@ -1527,15 +1609,15 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Set the current user to be authenticated, save the authentication token try - { + { AlfrescoClientInfo alfClient = (AlfrescoClientInfo) client; - getAuthenticationComponent().setCurrentUser( mapUserNameToPerson(krbDetails.getUserName())); + getAuthenticationComponent().setCurrentUser( mapUserNameToPerson(krbDetails.getUserName(), true)); alfClient.setAuthenticationTicket(getAuthenticationService().getCurrentTicket() ); } catch (AuthenticationException e) { // Invalid user or max tickets exceeded. Return a logon failure status - + logger.error("invalid user or tickets exceeded"); throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } @@ -1570,26 +1652,22 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement } else { - // Debug + logger.error( "No SPNEGO response, Kerberos logon failed"); - if ( logger.isDebugEnabled()) - logger.debug( "No SPNEGO response, Kerberos logon failed"); - - // Return a logon failure status - + // Return a logon failure status throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } catch (Exception ex) { - // Log the error - - if ( logger.isErrorEnabled()) { - logger.error("Kerberos logon error"); - logger.error(ex); - } + + if(ex instanceof SMBSrvException) + { + throw (SMBSrvException)ex; + } // Return a logon failure status + logger.error("Error during kerberos authentication", ex); throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } @@ -1599,6 +1677,20 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement return negTokenTarg; } + private String normalizeUserId(String externalUserId) throws SMBSrvException + { + try + { + return mapUserNameToPerson(externalUserId, true); + } + catch (AuthenticationException e) + { + // Invalid user. Return a logon failure status + logger.debug("Authentication Exception", e); + throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); + } + } + /** * Perform an NTLMv1 logon using the NTLMSSP type3 message * @@ -1616,11 +1708,11 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement { // NTLMv1 password hashes not accepted - logger.warn("NTLMv1 not accepted, client " + sess.getRemoteName()); + logger.error("NTLMv1 not accepted, client " + sess.getRemoteName()); // Return a logon failure - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } // Get the type 2 message that contains the challenge sent to the client @@ -1653,7 +1745,8 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Get the stored MD4 hashed password for the user, or null if the user does not exist - String md4hash = getNTLMAuthenticator().getMD4HashedPassword(userName); + String normalized = normalizeUserId(userName); + String md4hash = getNTLMAuthenticator().getMD4HashedPassword(normalized); if ( md4hash != null) { @@ -1689,23 +1782,31 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement if ( i != clientHash.length) { // Return a logon failure + + if(logger.isDebugEnabled()) + { + logger.debug("NTLMV1 - hash was not equal"); + } - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } - // Setup the Acegi authenticated user - + /* + * Setup the Alfresco Security (Acegi) context + */ try { - AlfrescoClientInfo alfClient = (AlfrescoClientInfo) client; - getAuthenticationComponent().setCurrentUser( mapUserNameToPerson(userName)); - alfClient.setAuthenticationTicket(getAuthenticationService().getCurrentTicket()); + AlfrescoClientInfo alfClient = (AlfrescoClientInfo) client; + getAuthenticationComponent().setCurrentUser( normalized); + alfClient.setAuthenticationTicket(getAuthenticationService().getCurrentTicket() ); + + } catch (AuthenticationException e) { // Invalid user or max tickets exceeded. Return a logon failure status - + logger.debug("Authentication Exception", e); throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } @@ -1720,13 +1821,14 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement } else { - // Log a warning, user does not exist - - logger.warn("User does not exist, " + userName); + if(logger.isDebugEnabled()) + { + logger.debug("User does not exist, " + userName); + } // Return a logon failure - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } else @@ -1737,7 +1839,7 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Return a logon failure - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } @@ -1761,7 +1863,12 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Return a logon failure - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); + } + + if(logger.isDebugEnabled()) + { + logger.debug("START doNTLMv1Logon:" + client); } // Check if we are using local MD4 password hashes or passthru authentication @@ -1775,7 +1882,9 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // DEBUG if ( logger.isDebugEnabled()) + { logger.debug("Null logon"); + } // Indicate a null logon in the client information @@ -1785,7 +1894,8 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Get the stored MD4 hashed password for the user, or null if the user does not exist - String md4hash = getNTLMAuthenticator().getMD4HashedPassword(client.getUserName()); + String normalized = normalizeUserId(client.getUserName()); + String md4hash = getNTLMAuthenticator().getMD4HashedPassword(normalized); if ( md4hash != null) { @@ -1816,6 +1926,9 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement } catch (NoSuchAlgorithmException ex) { + logger.error("Unable to encrypt challenge", ex); + + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrSrv, SMBStatus.SRVInternalServerError); } // Validate the password @@ -1832,8 +1945,12 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement if ( i != clientHash.length) { // Return a logon failure + if(logger.isDebugEnabled()) + { + logger.debug("NTLMV1 access denied - wrong password"); + } - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrSrv, SMBStatus.SRVBadPassword); } } @@ -1842,12 +1959,13 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement try { AlfrescoClientInfo alfClient = (AlfrescoClientInfo) client; - getAuthenticationComponent().setCurrentUser( mapUserNameToPerson(client.getUserName())); + getAuthenticationComponent().setCurrentUser( normalized); alfClient.setAuthenticationTicket(getAuthenticationService().getCurrentTicket()); } catch (AuthenticationException e) { // Invalid user or max tickets exceeded. Return a logon failure status + logger.debug("Authentication exception", e); throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } @@ -1867,18 +1985,18 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Return a logon failure - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } else { // Log a warning, authentication component does not support MD4 hashed passwords - logger.warn("Authentication component does not support MD4 password hashes"); + logger.error("Authentication component does not support MD4 password hashes"); // Return a logon failure - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } @@ -1900,6 +2018,11 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Check if we are using local MD4 password hashes or passthru authentication + if(logger.isDebugEnabled()) + { + logger.debug("START doNTLMv2Logon:" + client); + } + if ( getNTLMAuthenticator().getNTLMMode() == NTLMMode.MD4_PROVIDER) { // Get the NTLM logon details @@ -1913,7 +2036,9 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // DEBUG if ( logger.isDebugEnabled()) + { logger.debug("Null logon"); + } // Indicate a null logon in the client information @@ -1923,7 +2048,8 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Get the stored MD4 hashed password for the user, or null if the user does not exist - String md4hash = getNTLMAuthenticator().getMD4HashedPassword(userName); + String normalized = normalizeUserId(userName); + String md4hash = getNTLMAuthenticator().getMD4HashedPassword(normalized); if ( md4hash != null) { @@ -1953,19 +2079,24 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement if ( i != clientHmac.length) { // Return a logon failure + if(logger.isDebugEnabled()) + { + logger.debug("wrong password"); + } - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrSrv, SMBStatus.SRVBadPassword); } } // Setup the Acegi authenticated user AlfrescoClientInfo alfClient = (AlfrescoClientInfo) client; - getAuthenticationComponent().setCurrentUser( mapUserNameToPerson( userName)); + getAuthenticationComponent().setCurrentUser( normalized); alfClient.setAuthenticationTicket(getAuthenticationService().getCurrentTicket()); + // Store the full user name in the client information, indicate that this is not a guest logon - + client.setUserName( userName.toLowerCase()); client.setGuest( false); @@ -1975,31 +2106,35 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement } catch ( Exception ex) { + if(ex instanceof SMBSrvException) + { + throw (SMBSrvException)ex; + } + // Log the error if (ex instanceof AuthenticationException) { - logger.debug(ex); + logger.debug(ex.getMessage(), ex); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } else { - logger.error(ex); + logger.error(ex.getMessage(), ex); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } - // Return a logon failure - - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); } } else { // Log a warning, user does not exist - logger.warn("User does not exist, " + userName); + logger.warn("MD4Hash for User does not exist, " + userName); // Return a logon failure - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } else @@ -2010,7 +2145,7 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Return a logon failure - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } @@ -2026,6 +2161,11 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement { // Check if we are using local MD4 password hashes or passthru authentication + if(logger.isDebugEnabled()) + { + logger.debug("START doNTLMv2Logon:" + client); + } + if ( getNTLMAuthenticator().getNTLMMode() == NTLMMode.MD4_PROVIDER) { // Check for a null logon @@ -2035,7 +2175,9 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // DEBUG if ( logger.isDebugEnabled()) + { logger.debug("Null logon"); + } // Indicate a null logon in the client information @@ -2045,7 +2187,8 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Get the stored MD4 hashed password for the user, or null if the user does not exist - String md4hash = getNTLMAuthenticator().getMD4HashedPassword(client.getUserName()); + String normalized = normalizeUserId(client.getUserName()); + String md4hash = getNTLMAuthenticator().getMD4HashedPassword(normalized); if ( md4hash != null) { @@ -2084,16 +2227,17 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement if ( i != clientHmac.length) { + logger.debug("bad client hmac"); + // Return a logon failure - - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrSrv, SMBStatus.SRVBadPassword); } } // Setup the Acegi authenticated user AlfrescoClientInfo alfClient = (AlfrescoClientInfo) client; - getAuthenticationComponent().setCurrentUser( mapUserNameToPerson( client.getUserName())); + getAuthenticationComponent().setCurrentUser( normalized); alfClient.setAuthenticationTicket(getAuthenticationService().getCurrentTicket()); // Store the full user name in the client information, indicate that this is not a guest logon @@ -2110,16 +2254,20 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement if (ex instanceof AuthenticationException) { - logger.debug(ex); + // Return a logon failure + logger.debug(ex.getMessage(), ex); + + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } else { - logger.error(ex); + logger.error(ex.getMessage(), ex); + + // Return a logon failure + + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } - // Return a logon failure - - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); } } else @@ -2130,7 +2278,7 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Return a logon failure - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } else @@ -2141,7 +2289,7 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Return a logon failure - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } @@ -2186,7 +2334,8 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Get the stored MD4 hashed password for the user, or null if the user does not exist - String md4hash = getNTLMAuthenticator().getMD4HashedPassword(userName); + String normalized = normalizeUserId(userName); + String md4hash = getNTLMAuthenticator().getMD4HashedPassword(normalized); if ( md4hash != null) { @@ -2217,8 +2366,6 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement } catch ( NoSuchAlgorithmException ex) { - // Log the error - logger.error( ex); } @@ -2256,9 +2403,8 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement if ( i != clientHash.length) { - // Return a logon failure - - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + logger.debug("bad client hash"); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrSrv, SMBStatus.SRVBadPassword); } } @@ -2267,12 +2413,14 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement try { AlfrescoClientInfo alfClient = (AlfrescoClientInfo) client; - getAuthenticationComponent().setCurrentUser( mapUserNameToPerson( userName)); + getAuthenticationComponent().setCurrentUser( normalized); alfClient.setAuthenticationTicket(getAuthenticationService().getCurrentTicket()); } catch (AuthenticationException e) { // Invalid user or max tickets exceeded. Return a logon failure status + + logger.debug("Authentication exception", e); throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } @@ -2294,7 +2442,7 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Return a logon failure - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } else @@ -2305,7 +2453,7 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Return a logon failure - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } @@ -2323,7 +2471,7 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement if (reqPkt.checkPacketIsValid(13, 0) == false) { - throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv); + throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.ErrSrv, SMBStatus.SRVNonSpecificError); } // Extract the session details @@ -2354,7 +2502,8 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement if (user == null) { - throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv); + logger.error("User not specified"); + throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.ErrSrv, SMBStatus.SRVNonSpecificError); } // Extract the clients primary domain name string @@ -2370,7 +2519,8 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement if (domain == null) { - throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv); + logger.error("Domain not specified"); + throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.ErrSrv, SMBStatus.SRVNonSpecificError); } } @@ -2387,13 +2537,14 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement if (clientOS == null) { - throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv); + logger.error("client OS not specified"); + throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.ErrSrv, SMBStatus.SRVNonSpecificError); } } // DEBUG - if (logger.isDebugEnabled() && sess.hasDebug(SMBSrvSession.DBG_NEGOTIATE)) + if (logger.isDebugEnabled()) { logger.debug("NT Session setup from user=" + user + ", password=" + (uniPwd != null ? HexDump.hexString(uniPwd) : "none") + ", ANSIpwd=" @@ -2441,7 +2592,9 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Debug if ( logger.isDebugEnabled()) + { logger.debug("Logged on using Hashed/NTLMv1"); + } } else if ( uniPwd.length > 0) { @@ -2452,7 +2605,9 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Debug if ( logger.isDebugEnabled()) + { logger.debug("Logged on using Hashed/NTLMv2"); + } } } @@ -2467,7 +2622,7 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // DEBUG - if (logger.isDebugEnabled() && sess.hasDebug(SMBSrvSession.DBG_NEGOTIATE)) + if (logger.isDebugEnabled()) logger.debug("User " + user + ", logged on as guest"); } @@ -2478,20 +2633,13 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement if ( uid == VirtualCircuit.InvalidUID) { - - // DEBUG + logger.error("Failed to allocate UID for virtual circuit, " + vc); - if ( logger.isDebugEnabled() && sess.hasDebug( SMBSrvSession.DBG_NEGOTIATE)) - logger.debug("Failed to allocate UID for virtual circuit, " + vc); - - // Failed to allocate a UID - - throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + // Failed to allocate a UID + throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } - else if ( logger.isDebugEnabled() && sess.hasDebug( SMBSrvSession.DBG_NEGOTIATE)) - { - // DEBUG - + else if ( logger.isDebugEnabled()) + { logger.debug("Allocated UID=" + uid + " for VC=" + vc); } diff --git a/source/java/org/alfresco/filesys/auth/cifs/PassthruCifsAuthenticator.java b/source/java/org/alfresco/filesys/auth/cifs/PassthruCifsAuthenticator.java index 84939fc8a4..d0d50c82e8 100644 --- a/source/java/org/alfresco/filesys/auth/cifs/PassthruCifsAuthenticator.java +++ b/source/java/org/alfresco/filesys/auth/cifs/PassthruCifsAuthenticator.java @@ -77,7 +77,7 @@ public class PassthruCifsAuthenticator extends CifsAuthenticatorBase implements { // Debug logging - private static final Log logger = LogFactory.getLog("org.alfresco.smb.protocol.auth"); + private static final Log logger = LogFactory.getLog(PassthruCifsAuthenticator.class); // Constants @@ -465,6 +465,58 @@ public class PassthruCifsAuthenticator extends CifsAuthenticatorBase implements respPkt.setByteCount(pos - respPkt.getByteOffset()); } + /** + * Process the CIFS session setup request packet and build the session setup response. + *

+ * This is the boundary between alfresco and JLAN. So is responsible for logging and + * ensuring that the exceptions are correct for JLAN. + *

+ * @param sess SMBSrvSession + * @param reqPkt SMBSrvPacket + * @exception SMBSrvException + */ + public void processSessionSetup(final SMBSrvSession sess, final SMBSrvPacket reqPkt) + throws SMBSrvException + { + try + { + processAlfrescoSessionSetup(sess, reqPkt); + } + catch (SMBSrvException e) + { + /* + * A JLAN SMBSrvException is not human readable so we need to log the + * error before throwing, rather than logging the error here. + */ + if(logger.isDebugEnabled()) + { + logger.debug("Returning SMBSrvException to JLAN", e); + } + throw e; + } + catch (AlfrescoRuntimeException a) + { + Throwable c = a.getCause(); + + if(c != null) + { + if(a.getCause() instanceof SMBSrvException) + { + logger.error(c.getMessage(), c); + throw (SMBSrvException)c; + } + } + logger.error(a.getMessage(), a); + throw new SMBSrvException( SMBStatus.NTAccessDenied, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); + + } + catch (Throwable t) + { + logger.error(t.getMessage(), t); + throw new SMBSrvException( SMBStatus.NTAccessDenied, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); + } + } + /** * Process the CIFS session setup request packet and build the session setup response * @@ -472,14 +524,20 @@ public class PassthruCifsAuthenticator extends CifsAuthenticatorBase implements * @param reqPkt SMBSrvPacket * @exception SMBSrvException */ - public void processSessionSetup(SMBSrvSession sess, SMBSrvPacket reqPkt) + public void processAlfrescoSessionSetup(SMBSrvSession sess, SMBSrvPacket reqPkt) throws SMBSrvException { // Check that the received packet looks like a valid NT session setup andX request if (reqPkt.checkPacketIsValid(12, 0) == false) - throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv); - + { + if(logger.isErrorEnabled()) + { + logger.error("Invalid packet received, return SMBStatus.NTInvalidParameter"); + } + throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.ErrSrv, SMBStatus.SRVNonSpecificError); + } + // Check if the request is using security blobs or the older hashed password format if ( reqPkt.getParameterCount() == 13) @@ -518,28 +576,42 @@ public class PassthruCifsAuthenticator extends CifsAuthenticatorBase implements String domain = ""; - if (reqPkt.hasMoreData()) { + if (reqPkt.hasMoreData()) + { // Extract the callers domain name domain = reqPkt.unpackString(isUni); if (domain == null) - throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv); + { + if(logger.isErrorEnabled()) + { + logger.error("Invalid packet received, domain is null"); + } + throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.ErrSrv, SMBStatus.SRVNonSpecificError); + } } // Extract the clients native operating system String clientOS = ""; - if (reqPkt.hasMoreData()) { + if (reqPkt.hasMoreData()) + { // Extract the callers operating system name clientOS = reqPkt.unpackString(isUni); if (clientOS == null) - throw new SMBSrvException( SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv); + { + if(logger.isErrorEnabled()) + { + logger.error("Invalid packet received, client OS is null"); + } + throw new SMBSrvException( SMBStatus.NTInvalidParameter, SMBStatus.ErrSrv, SMBStatus.SRVNonSpecificError); + } } // Store the client maximum buffer size, maximum multiplexed requests count and client capability flags @@ -559,8 +631,9 @@ public class PassthruCifsAuthenticator extends CifsAuthenticatorBase implements // Set the remote address, if available if ( sess.hasRemoteAddress()) - client.setClientAddress(sess.getRemoteAddress().getHostAddress()); - + { + client.setClientAddress(sess.getRemoteAddress().getHostAddress()); + } // Set the process id for this client, for multi-stage logons client.setProcessId( reqPkt.getProcessId()); @@ -598,8 +671,9 @@ public class PassthruCifsAuthenticator extends CifsAuthenticatorBase implements // DEBUG if (logger.isDebugEnabled()) - logger.debug("NT Session setup NTLMSSP, MID=" + reqPkt.getMultiplexId() + ", UID=" + reqPkt.getUserId() + ", PID=" + reqPkt.getProcessId()); - + { + logger.debug("NT Session setup NTLMSSP, MID=" + reqPkt.getMultiplexId() + ", UID=" + reqPkt.getUserId() + ", PID=" + reqPkt.getProcessId()); + } // Process an NTLMSSP security blob respBlob = doNtlmsspSessionSetup( sess, client, buf, secBlobPos, secBlobLen, isUni); @@ -608,7 +682,12 @@ public class PassthruCifsAuthenticator extends CifsAuthenticatorBase implements { // Invalid blob type - throw new SMBSrvException( SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv); + if(logger.isErrorEnabled()) + { + logger.error("Invalid packet received for Passthru Cifs Autenticator, not of type NTLMSSP"); + } + + throw new SMBSrvException( SMBStatus.NTInvalidParameter, SMBStatus.ErrSrv, SMBStatus.SRVNonSpecificError); } } catch (SMBSrvException ex) @@ -624,9 +703,11 @@ public class PassthruCifsAuthenticator extends CifsAuthenticatorBase implements // Debug - if ( logger.isDebugEnabled() && sess.hasDebug(SMBSrvSession.DBG_NEGOTIATE)) + if ( logger.isDebugEnabled()) + { logger.debug("User " + client.getUserName() + " logged on " + (client != null ? " (type " + client.getLogonTypeString() + ")" : "")); - + } + // Update the client information if not already set if ( sess.getClientInformation() == null || @@ -719,17 +800,14 @@ public class PassthruCifsAuthenticator extends CifsAuthenticatorBase implements uid = sess.addVirtualCircuit( vc); if ( uid == VirtualCircuit.InvalidUID) - { - // DEBUG - - if ( logger.isDebugEnabled() && sess.hasDebug( SMBSrvSession.DBG_NEGOTIATE)) - logger.debug("Failed to allocate UID for virtual circuit, " + vc); + { + logger.error("Failed to allocate UID for virtual circuit, " + vc); // Failed to allocate a UID - - throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } - else if ( logger.isDebugEnabled() && sess.hasDebug( SMBSrvSession.DBG_NEGOTIATE)) { + else if ( logger.isDebugEnabled()) + { // DEBUG @@ -800,12 +878,9 @@ public class PassthruCifsAuthenticator extends CifsAuthenticatorBase implements if ( msgType == -1) { - // DEBUG - - if ( logger.isDebugEnabled()) + if ( logger.isErrorEnabled()) { - logger.debug("Invalid NTLMSSP token received"); - logger.debug(" Token=" + HexDump.hexString( secbuf, secpos, seclen, " ")); + logger.error("Invalid NTLMSSP token received, Token=" + HexDump.hexString( secbuf, secpos, seclen, " ") ); } // Return a logon failure status @@ -875,22 +950,20 @@ public class PassthruCifsAuthenticator extends CifsAuthenticatorBase implements sess.removeSetupObject( client.getProcessId()); // Return a logon failure - - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + logger.error("NTLMSSP Logon failure - type 2 message not found"); + + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } // Determine if the client sent us NTLMv1 or NTLMv2 if ( type3Msg.hasFlag( NTLM.Flag128Bit) && type3Msg.hasFlag( NTLM.FlagNTLM2Key)) { - // Debug - - if ( logger.isDebugEnabled()) - logger.debug("Received NTLMSSP/NTLMv2, not supported"); + logger.error("Received NTLMSSP/NTLMv2, not supported"); // Return a logon failure - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } else { @@ -1042,10 +1115,12 @@ public class PassthruCifsAuthenticator extends CifsAuthenticatorBase implements } catch (Exception ex) { + if(ex instanceof SMBSrvException) + { + throw (SMBSrvException)ex; + } - // Debug - - logger.error(ex.getMessage()); + logger.error("unable to log on "+ ex.getMessage(), ex); // Indicate logon failure @@ -1067,27 +1142,21 @@ public class PassthruCifsAuthenticator extends CifsAuthenticatorBase implements AuthenticateSession authSess = passDetails.getAuthenticateSession(); authSess.CloseSession(); - // DEBUG - if (logger.isDebugEnabled()) + { logger.debug("Closed auth session, sessId=" + authSess.getSessionId()); + } } catch (Exception ex) { - // Debug - logger.error("Passthru error closing session (auth user)", ex); } } } else { - - // DEBUG - - if (logger.isDebugEnabled()) - logger.debug(" No PassthruDetails for " + sess.getUniqueId() + ", check server list/domain mappings"); + logger.error(" No PassthruDetails for " + sess.getUniqueId() + ", check server list/domain mappings"); // Indicate logon failure diff --git a/source/java/org/alfresco/filesys/auth/cifs/package-info.java b/source/java/org/alfresco/filesys/auth/cifs/package-info.java index b5e6184554..725016bc23 100644 --- a/source/java/org/alfresco/filesys/auth/cifs/package-info.java +++ b/source/java/org/alfresco/filesys/auth/cifs/package-info.java @@ -1,3 +1,29 @@ +/* + * 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 . + */ /** + * Provides authentication implementations for CIFS + *

+ * AlfrescoCifsAuthenticator + * EnterpriseCifsAuthenticator deals with Kerberos, NTLMv1 and NTLMv2 + * PassthruCifsAuthenticator deals with authenticating against an external system + * + *

+ * CifsAuthenticatorBase abstract base class. */ package org.alfresco.filesys.auth.cifs; diff --git a/source/java/org/alfresco/filesys/avm/AVMDiskDriver.java b/source/java/org/alfresco/filesys/avm/AVMDiskDriver.java index f3f7923d1a..6011e7f23c 100644 --- a/source/java/org/alfresco/filesys/avm/AVMDiskDriver.java +++ b/source/java/org/alfresco/filesys/avm/AVMDiskDriver.java @@ -2027,7 +2027,7 @@ public class AVMDiskDriver extends AlfrescoTxDiskDriver implements DiskInterface { // Check if the file is a directory, or only has read access - if (file.getGrantedAccess() == NetworkFile.READONLY) + if (file.getGrantedAccess() <= NetworkFile.READONLY) throw new AccessDeniedException(); // If the content channel is not open for the file then start a transaction @@ -2081,7 +2081,7 @@ public class AVMDiskDriver extends AlfrescoTxDiskDriver implements DiskInterface { // Check if the file is a directory, or only has read access - if (file.isDirectory() || file.getGrantedAccess() == NetworkFile.READONLY) + if (file.isDirectory() || file.getGrantedAccess() <= NetworkFile.READONLY) throw new AccessDeniedException(); // If the content channel is not open for the file, or the channel is not writable, then start a transaction diff --git a/source/java/org/alfresco/filesys/avm/AVMNetworkFile.java b/source/java/org/alfresco/filesys/avm/AVMNetworkFile.java index 1f57488937..800922534d 100644 --- a/source/java/org/alfresco/filesys/avm/AVMNetworkFile.java +++ b/source/java/org/alfresco/filesys/avm/AVMNetworkFile.java @@ -462,7 +462,7 @@ public class AVMNetworkFile extends AlfrescoNetworkFile { // We need to create the channel - if (write && getGrantedAccess() == NetworkFile.READONLY) + if (write && getGrantedAccess() <= NetworkFile.READONLY) throw new AccessDeniedException("The network file was created for read-only: " + this); // Access the content data and get a file channel to the data diff --git a/source/java/org/alfresco/filesys/repo/ContentDiskDriver.java b/source/java/org/alfresco/filesys/repo/ContentDiskDriver.java index d98c2d67d0..cb3eb2a2e8 100644 --- a/source/java/org/alfresco/filesys/repo/ContentDiskDriver.java +++ b/source/java/org/alfresco/filesys/repo/ContentDiskDriver.java @@ -1027,8 +1027,8 @@ public class ContentDiskDriver extends AlfrescoTxDiskDriver implements DiskInter // Copy cached timestamps - if ( fstate.hasAccessDateTime()) - finfo.setAccessDateTime(fstate.getAccessDateTime()); +// if ( fstate.hasAccessDateTime()) +// finfo.setAccessDateTime(fstate.getAccessDateTime()); if ( fstate.hasChangeDateTime()) finfo.setChangeDateTime(fstate.getChangeDateTime()); if ( fstate.hasModifyDateTime()) @@ -1731,7 +1731,7 @@ public class ContentDiskDriver extends AlfrescoTxDiskDriver implements DiskInter throw new AccessDeniedException("Invalid access mode"); } - if ( fstate.getOpenCount() > 0) { + if ( fstate.getOpenCount() > 0 && params.isAttributesOnlyAccess() == false) { // Check for impersonation security level from the original process that opened the file @@ -1819,7 +1819,7 @@ public class ContentDiskDriver extends AlfrescoTxDiskDriver implements DiskInter { // Check if the file is already opened by this client/process - if ( tree.openFileCount() > 0) { + if ( tree.openFileCount() > 0 && params.isAttributesOnlyAccess() == false) { // Search the open file table for this session/virtual circuit @@ -1856,7 +1856,7 @@ public class ContentDiskDriver extends AlfrescoTxDiskDriver implements DiskInter if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE)) logger.debug("Re-use existing file open Path " + params.getPath() + ", PID=" + params.getProcessId() + ", params=" + ( params.isReadOnlyAccess() ? "ReadOnly" : "Write") + ", file=" + - ( contentFile.getGrantedAccess() == NetworkFile.READONLY ? "ReadOnly" : "Write")); + ( contentFile.getGrantedAccess() <= NetworkFile.READONLY ? "ReadOnly" : "Write")); } else if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE)) logger.debug("Not re-using file path=" + params.getPath() + ", readWrite=" + (params.isReadWriteAccess() ? "true" : "false") + @@ -1877,7 +1877,7 @@ public class ContentDiskDriver extends AlfrescoTxDiskDriver implements DiskInter // Create a new network file for the open request - netFile = ContentNetworkFile.createFile(nodeService, contentService, mimetypeService, cifsHelper, nodeRef, params.getPath(), params.isReadOnlyAccess(), sess); + netFile = ContentNetworkFile.createFile(nodeService, contentService, mimetypeService, cifsHelper, nodeRef, params.getPath(), params.isReadOnlyAccess(), params.isAttributesOnlyAccess(), sess); } } else @@ -1961,7 +1961,8 @@ public class ContentDiskDriver extends AlfrescoTxDiskDriver implements DiskInter // Update the file state, cache the node - fstate.incrementOpenCount(); + if ( netFile.getGrantedAccess() > NetworkFile.ATTRIBUTESONLY) + fstate.incrementOpenCount(); fstate.setFilesystemObject(nodeRef); // Store the state with the file @@ -1970,8 +1971,8 @@ public class ContentDiskDriver extends AlfrescoTxDiskDriver implements DiskInter // Set the file access date/time, if available - if ( fstate.hasAccessDateTime()) - netFile.setAccessDate( fstate.getAccessDateTime()); +// if ( fstate.hasAccessDateTime()) +// netFile.setAccessDate( fstate.getAccessDateTime()); } // Debug @@ -2090,7 +2091,7 @@ public class ContentDiskDriver extends AlfrescoTxDiskDriver implements DiskInter // Create the network file - ContentNetworkFile netFile = ContentNetworkFile.createFile(nodeService, contentService, mimetypeService, cifsHelper, result.getSecond(), params.getPath(), params.isReadOnlyAccess(), sess); + ContentNetworkFile netFile = ContentNetworkFile.createFile(nodeService, contentService, mimetypeService, cifsHelper, result.getSecond(), params.getPath(), params.isReadOnlyAccess(), params.isAttributesOnlyAccess(), sess); // Always allow write access to a newly created file @@ -2480,7 +2481,7 @@ public class ContentDiskDriver extends AlfrescoTxDiskDriver implements DiskInter // If the file open count is now zero then reset the stored sharing mode - if ( fstate.decrementOpenCount() == 0) + if ( file.getGrantedAccess() > NetworkFile.ATTRIBUTESONLY && fstate.decrementOpenCount() == 0) fstate.setSharedAccess( SharingMode.READWRITE + SharingMode.DELETE); // Check if there is a cached modification timestamp to be written out @@ -3433,11 +3434,9 @@ public class ContentDiskDriver extends AlfrescoTxDiskDriver implements DiskInter } catch (AlfrescoRuntimeException ex) { - // Debug - - if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME)) - logger.debug("Rename file", ex); - + // Unexpected Exception being consumed here - hence the error logging. + logger.error("Unable to rename file" + oldName, ex); + // Convert to a general I/O exception throw new AccessDeniedException("Rename file " + oldName); diff --git a/source/java/org/alfresco/filesys/repo/ContentDiskDriver2.java b/source/java/org/alfresco/filesys/repo/ContentDiskDriver2.java index 41b86929ec..52343746da 100644 --- a/source/java/org/alfresco/filesys/repo/ContentDiskDriver2.java +++ b/source/java/org/alfresco/filesys/repo/ContentDiskDriver2.java @@ -2100,6 +2100,11 @@ public class ContentDiskDriver2 extends AlfrescoDiskDriver implements ExtendedD diskDev.setTotalUnits( totalSpace / DiskSizeInterfaceConsts.DiskAllocationUnit); diskDev.setFreeUnits( freeSpace / DiskSizeInterfaceConsts.DiskAllocationUnit); + + if(logger.isDebugEnabled()) + { + logger.debug("getDiskInformation returning diskDev:" + diskDev); + } } public void setCifsHelper(CifsHelper cifsHelper) @@ -2145,16 +2150,6 @@ public class ContentDiskDriver2 extends AlfrescoDiskDriver implements ExtendedD logger.debug("processIOControl ctrlCode: 0x" + Integer.toHexString(ctrlCode) + ", fid:" + fid); } - NetworkFile netFile = tree.findFile(fid); - if ( netFile == null || netFile.isDirectory() == false) - { - if(logger.isDebugEnabled()) - { - logger.debug("net file is null or not a directory"); - } - throw new SMBException(SMBStatus.NTErr, SMBStatus.NTInvalidParameter); - } - final ContentContext ctx = (ContentContext) tree.getContext(); try { @@ -2170,7 +2165,14 @@ public class ContentDiskDriver2 extends AlfrescoDiskDriver implements ExtendedD } throw smbException; } - + catch(IOControlNotImplementedException ioException) + { + if(logger.isDebugEnabled()) + { + logger.debug("IO Control Not Implemented Exception fid:" + fid, ioException); + } + throw ioException; + } } @@ -2446,7 +2448,7 @@ public class ContentDiskDriver2 extends AlfrescoDiskDriver implements ExtendedD case READ_ONLY: logger.debug("open file for read only"); - netFile = ContentNetworkFile.createFile(nodeService, contentService, mimetypeService, getCifsHelper(), nodeRef, path, true, session); + netFile = ContentNetworkFile.createFile(nodeService, contentService, mimetypeService, getCifsHelper(), nodeRef, path, true, false, session); netFile.setGrantedAccess( NetworkFile.READONLY); break; @@ -2484,14 +2486,14 @@ public class ContentDiskDriver2 extends AlfrescoDiskDriver implements ExtendedD case ATTRIBUTES_ONLY: logger.debug("open file for attributes only"); - netFile = ContentNetworkFile.createFile(nodeService, contentService, mimetypeService, getCifsHelper(), nodeRef, path, true, session); + netFile = ContentNetworkFile.createFile(nodeService, contentService, mimetypeService, getCifsHelper(), nodeRef, path, true, true, session); netFile.setGrantedAccess( NetworkFile.READONLY); break; case DELETE: //TODO Not sure about this one. logger.debug("open file for delete"); - netFile = ContentNetworkFile.createFile(nodeService, contentService, mimetypeService, getCifsHelper(), nodeRef, path,true , session); + netFile = ContentNetworkFile.createFile(nodeService, contentService, mimetypeService, getCifsHelper(), nodeRef, path, true, false, session); netFile.setGrantedAccess( NetworkFile.READONLY); break; @@ -2551,7 +2553,7 @@ public class ContentDiskDriver2 extends AlfrescoDiskDriver implements ExtendedD urlStr.append( srvName); urlStr.append("/"); urlStr.append( tree.getSharedDevice().getName()); - urlStr.append( pathl); + urlStr.append( path); urlStr.append("\r\n"); // Create the in memory pseudo file for the URL link diff --git a/source/java/org/alfresco/filesys/repo/ContentDiskDriverTest.java b/source/java/org/alfresco/filesys/repo/ContentDiskDriverTest.java index e820644f99..dffcaf542d 100644 --- a/source/java/org/alfresco/filesys/repo/ContentDiskDriverTest.java +++ b/source/java/org/alfresco/filesys/repo/ContentDiskDriverTest.java @@ -2467,6 +2467,252 @@ public class ContentDiskDriverTest extends TestCase } // testScenarioShuffleMetadataExtraction + + /** + * ALF-12812 + * + * This test tries to simulate the shuffling that is done by MS Word 2011 for Mac + * with regard to metadata extraction. In particular the temporary file names are + * different. + *

+ * 1: Setup an update rule for ContentMetadataExtractor. + * Simulate a WORD 2011 for Mac Create + * 2: Write "Word Work File D_1725484373.tmp" + * 3: Close file + * 4: Rename "Word Work File D_1725484373.tmp" to ContentDiskDriver.docx + * 5: Check metadata extraction + */ + public void testMetadataExtractionForMac() throws Exception + { + logger.debug("testMetadataExtractionForMac"); + final String FILE_NAME = "ContentDiskDriver.docx"; + //final String FILE_OLD_TEMP = "._Word Work File D_1725484373.tmp"; + final String FILE_NEW_TEMP = "Word Work File D_1725484373.tmp"; + + class TestContext + { + NodeRef testDirNodeRef; + NodeRef testNodeRef; + NetworkFile firstFileHandle; +// NetworkFile secondFileHandle; + }; + + final TestContext testContext = new TestContext(); + + final String TEST_DIR = TEST_ROOT_DOS_PATH + "\\testMetadataExtractionForMac"; + + 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); + + UserTransaction txn = transactionService.getUserTransaction(); + + return null; + + + } + }; + tran.doInTransaction(createTestDirCB); + logger.debug("Create rule on test dir"); + + RetryingTransactionCallback createRuleCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + Rule rule = new Rule(); + rule.setRuleType(RuleType.UPDATE); + rule.applyToChildren(true); + rule.setRuleDisabled(false); + rule.setTitle("Extract Metadata from update content"); + rule.setDescription("ContentDiskDriverTest"); + + Map props = new HashMap(1); + Action extractAction = actionService.createAction("extract-metadata", props); + + ActionCondition noCondition1 = actionService.createActionCondition(NoConditionEvaluator.NAME); + extractAction.addActionCondition(noCondition1); + + ActionCondition noCondition2 = actionService.createActionCondition(NoConditionEvaluator.NAME); + CompositeAction compAction = actionService.createCompositeAction(); + compAction.setTitle("Extract Metadata"); + compAction.setDescription("Content Disk Driver Test - Extract Metadata"); + compAction.addAction(extractAction); + compAction.addActionCondition(noCondition2); + + rule.setAction(compAction); + + ruleService.saveRule(testContext.testDirNodeRef, rule); + + logger.debug("rule created"); + + return null; + } + }; + tran.doInTransaction(createRuleCB, false, true); + + /** + * 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 file we are going to use to test + */ + FileOpenParams createFileParams = new FileOpenParams(TEST_DIR + "\\" + FILE_NEW_TEMP, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + testContext.firstFileHandle = driver.createFile(testSession, testConnection, createFileParams); + assertNotNull("first file Handle is null", 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_NEW_TEMP); + assertNotNull("testContext.testNodeRef is null", testContext.testNodeRef); + + // test non CM namespace property + nodeService.setProperty(testContext.testNodeRef, TransferModel.PROP_ENABLED, true); + + // Check that the temporary aspect has been applied. + assertTrue("temporary aspect not applied", nodeService.hasAspect(testContext.testNodeRef, ContentModel.ASPECT_TEMPORARY)); + return null; + } + }; + tran.doInTransaction(createFileCB, false, true); + + logger.debug("step b: write content to test file"); + + /** + * Write ContentDiskDriverTest1.docx to the test file, + */ + RetryingTransactionCallback writeFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + ClassPathResource fileResource = new ClassPathResource("filesys/ContentDiskDriverTest1.docx"); + assertNotNull("unable to find test resource filesys/ContentDiskDriverTest1.docx", fileResource); + + byte[] buffer= new byte[1000]; + InputStream is = fileResource.getInputStream(); + try + { + long offset = 0; + int i = is.read(buffer, 0, buffer.length); + while(i > 0) + { + testContext.firstFileHandle.writeFile(buffer, i, 0, offset); + offset += i; + i = is.read(buffer, 0, buffer.length); + } + } + finally + { + is.close(); + } + + driver.closeFile(testSession, testConnection, testContext.firstFileHandle); + + return null; + } + }; + tran.doInTransaction(writeFileCB, false, true); + + logger.debug("Step b: rename the test file."); + + /** + * 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); + + logger.debug("Step c: validate metadata has been extracted."); + /** + * c: check simple case of meta-data extraction has worked. + */ + RetryingTransactionCallback validateFirstExtractionCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + Map props = nodeService.getProperties(testContext.testNodeRef); + + assertTrue("Enabled property has been lost", props.containsKey(TransferModel.PROP_ENABLED)); + + // Check that the temporary aspect has been applied. + assertTrue("temporary aspect has not been removed", !nodeService.hasAspect(testContext.testNodeRef, ContentModel.ASPECT_TEMPORARY)); + + + // These metadata values should be extracted. + assertEquals("description is not correct", "This is a test file", nodeService.getProperty(testContext.testNodeRef, ContentModel.PROP_DESCRIPTION)); + assertEquals("title is not correct", "ContentDiskDriverTest", nodeService.getProperty(testContext.testNodeRef, ContentModel.PROP_TITLE)); + assertEquals("author is not correct", "mrogers", nodeService.getProperty(testContext.testNodeRef, ContentModel.PROP_AUTHOR)); + + ContentData data = (ContentData)props.get(ContentModel.PROP_CONTENT); + assertEquals("mimeType is wrong", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", data.getMimetype()); + assertEquals("size is wrong", 11302, data.getSize()); + + return null; + } + }; + tran.doInTransaction(validateFirstExtractionCB, false, true); + + + } // testScenarioMetadataExtractionForMac + public void testDirListing()throws Exception { logger.debug("testDirListing"); @@ -4352,7 +4598,7 @@ public class ContentDiskDriverTest extends TestCase * e) Lock file deleted * */ - public void testScenarioMacLionTextEdit() throws Exception + public void DISABLED_TestScenarioMacLionTextEdit() throws Exception { logger.debug("testScenarioLionTextEdit"); final String FILE_NAME = "test.txt"; diff --git a/source/java/org/alfresco/filesys/repo/ContentIOControlHandler.java b/source/java/org/alfresco/filesys/repo/ContentIOControlHandler.java index 29df048bac..6c80f5d8b7 100644 --- a/source/java/org/alfresco/filesys/repo/ContentIOControlHandler.java +++ b/source/java/org/alfresco/filesys/repo/ContentIOControlHandler.java @@ -116,7 +116,8 @@ public class ContentIOControlHandler implements IOControlHandler * @param dataBuf I/O control specific input data * @param isFSCtrl true if this is a filesystem control, or false for a device control * @param filter if bit0 is set indicates that the control applies to the share root handle - * @return DataBuffer + * @return DataBuffer or null if there is no response buffer. + * * @exception IOControlNotImplementedException * @exception SMBException */ @@ -125,10 +126,12 @@ public class ContentIOControlHandler implements IOControlHandler throws IOControlNotImplementedException, SMBException { // Validate the file id - NetworkFile netFile = tree.findFile(fid); - if ( netFile == null || netFile.isDirectory() == false) + if ( netFile == null ) + { + logger.debug("IO Control Handler called with missing file"); throw new SMBException(SMBStatus.NTErr, SMBStatus.NTInvalidParameter); + } // Split the control code @@ -144,23 +147,36 @@ public class ContentIOControlHandler implements IOControlHandler // Create or get object id if ( ioFunc == NTIOCtl.FsCtlCreateOrGetObjectId) - return null; + { + logger.debug("Create or Get Object Id - return null"); + return null; +// +// logger.debug("Create or Get Object Id - throw not implemented exception"); +// throw new IOControlNotImplementedException("Create or Get Object Id not implemented"); +// //return null; + } } // Check if the I/O control looks like a custom I/O control request if ( devType != NTIOCtl.DeviceFileSystem || dataBuf == null) - throw new IOControlNotImplementedException(); + { + throw new IOControlNotImplementedException("Custom IO control request not implemented"); + } // Check if the request has a valid signature for an Alfresco CIFS server I/O control if ( dataBuf.getLength() < IOControl.Signature.length()) + { throw new IOControlNotImplementedException("Bad request length"); + } String sig = dataBuf.getFixedString(IOControl.Signature.length(), false); if ( sig == null || sig.compareTo(IOControl.Signature) != 0) + { throw new IOControlNotImplementedException("Bad request signature"); + } // Get the node for the parent folder, make sure it is a folder @@ -171,7 +187,9 @@ public class ContentIOControlHandler implements IOControlHandler folderNode = getNodeForPath(tree, netFile.getFullName()); if ( getCifsHelper().isDirectory( folderNode) == false) + { folderNode = null; + } } catch ( FileNotFoundException ex) { @@ -181,14 +199,17 @@ public class ContentIOControlHandler implements IOControlHandler // If the folder node is not valid return an error if ( folderNode == null) + { + logger.debug("unable to get parent folder - return access denied"); throw new SMBException(SMBStatus.NTErr, SMBStatus.NTAccessDenied); + } // Debug if ( logger.isDebugEnabled()) { - logger.debug("IO control func=0x" + Integer.toHexString(ioFunc) + ", fid=" + fid + ", buffer=" + dataBuf); - logger.debug(" Folder nodeRef=" + folderNode); + logger.debug("IO control func=0x" + Integer.toHexString(ioFunc) + ", fid=" + fid + ", buffer=" + dataBuf + + " Folder nodeRef=" + folderNode); } // Check if the I/O control code is one of our custom codes @@ -217,8 +238,11 @@ public class ContentIOControlHandler implements IOControlHandler // Get file information for a file within the current folder case IOControl.CmdFileStatus: - - + + if(logger.isDebugEnabled()) + { + logger.debug("CmdFileStatus"); + } // Process the file status request retBuffer = procIOFileStatus( sess, tree, dataBuf, folderNode, contentDriver, contentContext); @@ -229,6 +253,10 @@ public class ContentIOControlHandler implements IOControlHandler case IOControl.CmdGetActionInfo: // Process the get action information request + if(logger.isDebugEnabled()) + { + logger.debug("GetActionInfo"); + } retBuffer = procGetActionInfo(sess, tree, dataBuf, folderNode, netFile, contentDriver, contentContext); break; @@ -238,6 +266,10 @@ public class ContentIOControlHandler implements IOControlHandler case IOControl.CmdRunAction: // Process the run action request + if(logger.isDebugEnabled()) + { + logger.debug("RunAction"); + } retBuffer = procRunAction(sess, tree, dataBuf, folderNode, netFile, contentDriver, contentContext); break; @@ -247,6 +279,10 @@ public class ContentIOControlHandler implements IOControlHandler case IOControl.CmdGetAuthTicket: // Process the get auth ticket request + if(logger.isDebugEnabled()) + { + logger.debug("GetAuthTicket"); + } retBuffer = procGetAuthTicket(sess, tree, dataBuf, folderNode, netFile, contentDriver, contentContext); break; diff --git a/source/java/org/alfresco/filesys/repo/ContentNetworkFile.java b/source/java/org/alfresco/filesys/repo/ContentNetworkFile.java index 70ff97c51d..602c559700 100644 --- a/source/java/org/alfresco/filesys/repo/ContentNetworkFile.java +++ b/source/java/org/alfresco/filesys/repo/ContentNetworkFile.java @@ -88,7 +88,7 @@ public class ContentNetworkFile extends NodeRefNetworkFile * Helper method to create a {@link NetworkFile network file} given a node reference. */ public static ContentNetworkFile createFile( NodeService nodeService, ContentService contentService, MimetypeService mimetypeService, - CifsHelper cifsHelper, NodeRef nodeRef, String path, boolean readOnly, SrvSession sess) + CifsHelper cifsHelper, NodeRef nodeRef, String path, boolean readOnly, boolean attributesOnly, SrvSession sess) { // Create the file @@ -116,12 +116,13 @@ public class ContentNetworkFile extends NodeRefNetworkFile // Set relevant parameters - if (readOnly) - { + if (attributesOnly) { + netFile.setGrantedAccess( NetworkFile.ATTRIBUTESONLY); + } + else if (readOnly) { netFile.setGrantedAccess(NetworkFile.READONLY); } - else - { + else { netFile.setGrantedAccess(NetworkFile.READWRITE); } @@ -153,11 +154,15 @@ public class ContentNetworkFile extends NodeRefNetworkFile if ( fileInfo.hasCreationDateTime()) netFile.setCreationDate( fileInfo.getCreationDateTime()); - if ( fileInfo.hasModifyDateTime()) + if ( fileInfo.hasModifyDateTime() && fileInfo.getModifyDateTime() > 0L) netFile.setModifyDate(fileInfo.getModifyDateTime()); + else + netFile.setModifyDate(fileInfo.getCreationDateTime()); - if ( fileInfo.hasAccessDateTime()) + if ( fileInfo.hasAccessDateTime() && fileInfo.getAccessDateTime() > 0L) netFile.setAccessDate(fileInfo.getAccessDateTime()); + else + netFile.setAccessDate(fileInfo.getCreationDateTime()); // Set the file attributes diff --git a/source/java/org/alfresco/filesys/repo/ContentSearchContext.java b/source/java/org/alfresco/filesys/repo/ContentSearchContext.java index 8d12657ad1..595107dbc8 100644 --- a/source/java/org/alfresco/filesys/repo/ContentSearchContext.java +++ b/source/java/org/alfresco/filesys/repo/ContentSearchContext.java @@ -42,7 +42,8 @@ import org.apache.commons.logging.LogFactory; * * @author Derek Hulley */ -public class ContentSearchContext extends SearchContext +public class ContentSearchContext extends SearchContext + implements InFlightCorrectable { // Debug logging @@ -54,6 +55,13 @@ public class ContentSearchContext extends SearchContext public final static int LinkFileSize = 512; + private InFlightCorrector corrector; + + public void setInFlightCorrector(InFlightCorrector corrector) + { + this.corrector = corrector; + } + // List of nodes returned from the folder search private CifsHelper cifsHelper; @@ -163,7 +171,9 @@ public class ContentSearchContext extends SearchContext // Check if there is anything else to return if (!hasMoreFiles()) + { return false; + } // Increment the index and resume id @@ -231,17 +241,28 @@ public class ContentSearchContext extends SearchContext try { - // Get the file information and copy across to the callers file info + // Get the file information and copy across to the caller's file info nextInfo = cifsHelper.getFileInformation(nextNodeRef, "", false, false); info.copyFrom(nextInfo); + + /** + * Apply in flight correction + */ + if(corrector != null) + { + corrector.correct(info, m_relPath); + } + } catch ( InvalidNodeRefException ex) { // Log a warning if ( logger.isWarnEnabled()) + { logger.warn("Noderef " + nextNodeRef + " no longer valid, ignoring"); + } // Update the node index, node no longer exists, try the next node in the search @@ -253,7 +274,9 @@ public class ContentSearchContext extends SearchContext // Check if we have finished returning file info if ( nextInfo == null) + { return false; + } // Generate a file id for the current file diff --git a/source/java/org/alfresco/filesys/repo/FilesystemTransactionAdvice.java b/source/java/org/alfresco/filesys/repo/FilesystemTransactionAdvice.java index 2f36ae4111..ca934eab72 100644 --- a/source/java/org/alfresco/filesys/repo/FilesystemTransactionAdvice.java +++ b/source/java/org/alfresco/filesys/repo/FilesystemTransactionAdvice.java @@ -45,8 +45,6 @@ public class FilesystemTransactionAdvice implements MethodInterceptor { private boolean readOnly; -// private AlfrescoDiskDriver driver; - private TransactionService transactionService; public FilesystemTransactionAdvice() @@ -92,64 +90,34 @@ public class FilesystemTransactionAdvice implements MethodInterceptor } }; - if(readOnly) + try { - // read only transaction - try - { - return tran.doInTransaction(callback, true); - } - catch(PropagatingException pe) - { - Throwable t = pe.getCause(); - if(t != null) - { - if(t instanceof IOException) - { - throw (IOException) pe.getCause(); - } - if(t instanceof SMBException) - { - throw pe.getCause(); - } - if(t instanceof DeviceContextException) - { - throw pe.getCause(); - } - throw t; - } - throw pe; - } + return tran.doInTransaction(callback, readOnly); } - else + catch(PropagatingException pe) { - // read/write only transaction - try + Throwable t = pe.getCause(); + if(t != null) { - return tran.doInTransaction(callback); - } - catch(PropagatingException pe) - { - Throwable t = pe.getCause(); - if(t != null) + if(t instanceof IOException) + { + throw (IOException) t; + } + if(t instanceof IOControlNotImplementedException) + { + throw (IOControlNotImplementedException) t; + } + if(t instanceof SMBException) + { + throw (SMBException)t; + } + if(t instanceof DeviceContextException) { - if(t instanceof IOException) - { - // Unwrap checked exceptions - throw pe.getCause(); - } - if(t instanceof SMBException) - { - throw pe.getCause(); - } - if(t instanceof DeviceContextException) - { - throw pe.getCause(); - } throw t; } - throw pe; - } + throw t; + } + throw pe; } } diff --git a/source/java/org/alfresco/filesys/repo/InFlightCorrectable.java b/source/java/org/alfresco/filesys/repo/InFlightCorrectable.java new file mode 100644 index 0000000000..999ecb0b6b --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/InFlightCorrectable.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 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.filesys.repo; + +/** + * + * @author mrogers + * + */ +public interface InFlightCorrectable +{ + public void setInFlightCorrector(InFlightCorrector correctable); +} diff --git a/source/java/org/alfresco/filesys/repo/InFlightCorrector.java b/source/java/org/alfresco/filesys/repo/InFlightCorrector.java new file mode 100644 index 0000000000..5643ce384d --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/InFlightCorrector.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 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.filesys.repo; + +import java.util.Date; + +import org.alfresco.jlan.server.filesys.FileInfo; +import org.alfresco.jlan.server.filesys.TreeConnection; +import org.alfresco.jlan.server.filesys.cache.FileState; +import org.alfresco.jlan.server.filesys.cache.FileStateCache; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * The in flight corrector corrects search results that have not yet been committed to the + * repository. + * + * It substitutes the "in flight" valuses from the state cache in place of the values committed to + * the repo + * + * @author mrogers + */ +public interface InFlightCorrector +{ + /** + * Correct thr results with in flight details. + * @param info + * @param folderPath + */ + public void correct(FileInfo info, String folderPath); +} diff --git a/source/java/org/alfresco/filesys/repo/InFlightCorrectorImpl.java b/source/java/org/alfresco/filesys/repo/InFlightCorrectorImpl.java new file mode 100644 index 0000000000..e309436b37 --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/InFlightCorrectorImpl.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 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.filesys.repo; + +import java.util.Date; + +import org.alfresco.jlan.server.filesys.FileInfo; +import org.alfresco.jlan.server.filesys.TreeConnection; +import org.alfresco.jlan.server.filesys.cache.FileState; +import org.alfresco.jlan.server.filesys.cache.FileStateCache; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * The in flight corrector corrects search results that have not yet been committed to the + * repository. + * + * It substitutes the "in flight" valuses from the state cache in place of the values committed to + * the repo + * + * @author mrogers + */ +public class InFlightCorrectorImpl implements InFlightCorrector +{ + TreeConnection tree; + + private static final Log logger = LogFactory.getLog(InFlightCorrectorImpl.class); + + public InFlightCorrectorImpl(TreeConnection tree) + { + this.tree = tree; + } + public void correct(FileInfo info, String folderPath) + { + ContentContext tctx = (ContentContext) tree.getContext(); + + String path = folderPath + info.getFileName(); + + if(tctx.hasStateCache()) + { + FileStateCache cache = tctx.getStateCache(); + FileState fstate = cache.findFileState( path, true); + + if(fstate != null) + { + logger.debug("correct " + path); + /* + * What about stale file state values here? + */ + if(fstate.hasFileSize()) + { + if(logger.isDebugEnabled()) + { + logger.debug("replace file size " + info.getSize() + " with " + fstate.getFileSize()); + } + info.setFileSize(fstate.getFileSize()); + } + if ( fstate.hasAccessDateTime()) + { + if(logger.isDebugEnabled()) + { + logger.debug("replace access date " + new Date(info.getAccessDateTime()) + " with " + new Date(fstate.getAccessDateTime())); + } + info.setAccessDateTime(fstate.getAccessDateTime()); + } + if ( fstate.hasChangeDateTime()) + { + if(logger.isDebugEnabled()) + { + logger.debug("replace change date " + new Date(info.getChangeDateTime()) + " with " + new Date(fstate.getChangeDateTime())); + } + info.setChangeDateTime(fstate.getChangeDateTime()); + } + if ( fstate.hasModifyDateTime()) + { + if(logger.isDebugEnabled()) + { + logger.debug("replace modified date " + new Date(info.getModifyDateTime()) + " with " + new Date(fstate.getModifyDateTime())); + } + info.setModifyDateTime(fstate.getModifyDateTime()); + } + if ( fstate.hasAllocationSize()) + { + if(logger.isDebugEnabled()) + { + logger.debug("replace allocation size" + info.getAllocationSize() + " with " + fstate.getAllocationSize()); + } + info.setAllocationSize(fstate.getAllocationSize()); + } + } + } + } + +} diff --git a/source/java/org/alfresco/filesys/repo/LegacyFileStateDriver.java b/source/java/org/alfresco/filesys/repo/LegacyFileStateDriver.java index 44c5831f54..c5464d4add 100644 --- a/source/java/org/alfresco/filesys/repo/LegacyFileStateDriver.java +++ b/source/java/org/alfresco/filesys/repo/LegacyFileStateDriver.java @@ -301,28 +301,25 @@ public class LegacyFileStateDriver implements ExtendedDiskInterface { FileStateCache cache = tctx.getStateCache(); FileState fstate = cache.findFileState( param.getFullName(), true); - - if(fstate.getOpenCount() ==0 ) + + if(fstate != null && param.getAccessToken() != null) { - logger.debug("OpenCount = 0, reset shared access to READ WRITE DELETE"); - fstate.setSharedAccess( SharingMode.READWRITE + SharingMode.DELETE); - + FileAccessToken token = param.getAccessToken(); + if(logger.isDebugEnabled() && token != null) + { + logger.debug("close file, release access token:" + token); + } + cache.releaseFileAccess(fstate, token); + } + + if(fstate.getOpenCount() == 0 ) + { + logger.debug("fstate OpenCount == 0, reset in-flight state"); fstate.setAllocationSize(-1); fstate.setFileSize(-1); fstate.updateChangeDateTime(0); fstate.updateModifyDateTime(0); } - - if(fstate != null && param.getAccessToken() != null) - { - - FileAccessToken token = param.getAccessToken(); - if(logger.isDebugEnabled() && token != null) - { - logger.debug("close file release access token:" + token); - } - cache.releaseFileAccess(fstate, token); - } } } catch(IOException ie) @@ -459,7 +456,17 @@ public class LegacyFileStateDriver implements ExtendedDiskInterface public SearchContext startSearch(SrvSession sess, TreeConnection tree, String searchPath, int attrib) throws FileNotFoundException { - return diskInterface.startSearch(sess, tree, searchPath, attrib); + InFlightCorrector t = new InFlightCorrectorImpl(tree); + + SearchContext ctx = diskInterface.startSearch(sess, tree, searchPath, attrib); + + if(ctx instanceof InFlightCorrectable) + { + InFlightCorrectable thingable = (InFlightCorrectable)ctx; + thingable.setInFlightCorrector(t); + } + + return ctx; } diff --git a/source/java/org/alfresco/filesys/repo/TempNetworkFile.java b/source/java/org/alfresco/filesys/repo/TempNetworkFile.java index 62d4192f21..500e57b89d 100644 --- a/source/java/org/alfresco/filesys/repo/TempNetworkFile.java +++ b/source/java/org/alfresco/filesys/repo/TempNetworkFile.java @@ -84,6 +84,7 @@ public class TempNetworkFile extends JavaNetworkFile implements NetworkFileState fileState.updateModifyDateTime(); fileState.updateAccessDateTime(); fileState.setFileSize(size); + fileState.setAllocationSize((size + 512L) & 0xFFFFFFFFFFFFFE00L); } } @@ -102,6 +103,7 @@ public class TempNetworkFile extends JavaNetworkFile implements NetworkFileState fileState.updateModifyDateTime(); fileState.updateAccessDateTime(); fileState.setFileSize(size); + fileState.setAllocationSize((size + 512L) & 0xFFFFFFFFFFFFFE00L); } } @@ -121,6 +123,7 @@ public class TempNetworkFile extends JavaNetworkFile implements NetworkFileState fileState.updateModifyDateTime(); fileState.updateAccessDateTime(); fileState.setFileSize(size); + fileState.setAllocationSize((size + 512L) & 0xFFFFFFFFFFFFFE00L); } } diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioCreateDeleteRenameShuffle.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioCreateDeleteRenameShuffle.java new file mode 100644 index 0000000000..ad004c279d --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioCreateDeleteRenameShuffle.java @@ -0,0 +1,114 @@ +/* + * 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; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.alfresco.filesys.repo.rules.ScenarioInstance.Ranking; +import org.alfresco.filesys.repo.rules.operations.CreateFileOperation; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * A create, delete, rename, shuffle + * + * a) New file created. + * b) Existing file deleted + * c) New file moved into place. + */ +public class ScenarioCreateDeleteRenameShuffle implements Scenario +{ + private static Log logger = LogFactory.getLog(ScenarioCreateDeleteRenameShuffle.class); + + /** + * The regex pattern of a create that will trigger a new instance of + * the scenario. + */ + private Pattern pattern; + private String strPattern; + + + private long timeout = 30000; + + private Ranking ranking = Ranking.HIGH; + + @Override + public ScenarioInstance createInstance(final List currentInstances, Operation operation) + { + /** + * This scenario is triggered by a create of a file matching + * the pattern + */ + if(operation instanceof CreateFileOperation) + { + CreateFileOperation c = (CreateFileOperation)operation; + + Matcher m = pattern.matcher(c.getName()); + if(m.matches()) + { + if(logger.isDebugEnabled()) + { + logger.debug("New Scenario Create Delete Rename Shuffle Instance pattern:" + strPattern); + } + + ScenarioCreateDeleteRenameShuffleInstance instance = new ScenarioCreateDeleteRenameShuffleInstance() ; + instance.setTimeout(timeout); + instance.setRanking(ranking); + return instance; + } + } + + // No not interested. + return null; + + } + + public void setPattern(String pattern) + { + this.pattern = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE); + this.strPattern = pattern; + } + + public String getPattern() + { + return this.strPattern; + } + + public void setTimeout(long timeout) + { + this.timeout = timeout; + } + + public long getTimeout() + { + return timeout; + } + + public void setRanking(Ranking ranking) + { + this.ranking = ranking; + } + + public Ranking getRanking() + { + return ranking; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioCreateDeleteRenameShuffleInstance.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioCreateDeleteRenameShuffleInstance.java new file mode 100644 index 0000000000..179f70336f --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioCreateDeleteRenameShuffleInstance.java @@ -0,0 +1,295 @@ +/* + * 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; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import org.alfresco.filesys.repo.rules.ScenarioLockedDeleteShuffleInstance.InternalState; +import org.alfresco.filesys.repo.rules.commands.CompoundCommand; +import org.alfresco.filesys.repo.rules.commands.CopyContentCommand; +import org.alfresco.filesys.repo.rules.commands.DeleteFileCommand; +import org.alfresco.filesys.repo.rules.commands.RenameFileCommand; +import org.alfresco.filesys.repo.rules.operations.CreateFileOperation; +import org.alfresco.filesys.repo.rules.operations.DeleteFileOperation; +import org.alfresco.filesys.repo.rules.operations.MoveFileOperation; +import org.alfresco.filesys.repo.rules.operations.RenameFileOperation; +import org.alfresco.jlan.server.filesys.FileName; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * This is an instance of a create, delete, rename shuffle" triggered by a create of a + * file matching a specified pattern. + *

+ * a) New file created. Typically with an obscure name. + * b) Existing file deleted + * c) New file moved into place. + * + *

+ * If this filter is active then this is what happens. + * a) New file created. New file created (X). + * b) Existing file deleted (Y to Z). File moved to temporary location. + * c) Rename - Scenario fires + * - File moved back from temporary location + * - Content updated/ + * - temporary file deleted + */ +public class ScenarioCreateDeleteRenameShuffleInstance implements ScenarioInstance +{ + private static Log logger = LogFactory.getLog(ScenarioCreateDeleteRenameShuffleInstance.class); + + enum InternalState + { + NONE, + ACTIVE + } + + InternalState internalState = InternalState.NONE; + + private Date startTime = new Date(); + + private String createName; + private Ranking ranking; + private boolean checkFilename=true; + + /** + * Timeout in ms. Default 30 seconds. + */ + private long timeout = 60000; + + private boolean isComplete; + + /** + * Keep track of deletes that we substitute with a rename + * could be more than one if scenarios overlap + * + * From, TempFileName + */ + private Map deletes = new HashMap(); + + /** + * Evaluate the next operation + * @param operation + */ + public Command evaluate(Operation operation) + { + + /** + * Anti-pattern : timeout + */ + Date now = new Date(); + if(now.getTime() > startTime.getTime() + getTimeout()) + { + if(logger.isDebugEnabled()) + { + logger.debug("Instance timed out createName:" + createName); + isComplete = true; + return null; + } + } + + /** + * Anti-pattern for all states - delete the file we are + * shuffling + */ + if(createName != null) + { + if(operation instanceof DeleteFileOperation) + { + DeleteFileOperation d = (DeleteFileOperation)operation; + if(d.getName().equals(createName)) + { + if(logger.isDebugEnabled()) + { + logger.debug("Anti-pattern : Shuffle file deleted createName:" + createName); + } + isComplete = true; + return null; + } + } + } + + switch (internalState) + { + + case NONE: + // Looking for a create transition + if(operation instanceof CreateFileOperation) + { + CreateFileOperation c = (CreateFileOperation)operation; + this.createName = c.getName(); + if(logger.isDebugEnabled()) + { + logger.debug("entering ACTIVE state: " + createName); + } + internalState = InternalState.ACTIVE; + return null; + } + else + { + // anything else bomb out + if(logger.isDebugEnabled()) + { + logger.debug("State error, expected a CREATE"); + } + isComplete = true; + } + break; + + case ACTIVE: + + /** + * Looking for deletes and renames + */ + + /** + * Looking for target file being deleted + * + * Need to intervene and replace delete with a rename to temp file. + */ + if(operation instanceof DeleteFileOperation) + { + DeleteFileOperation d = (DeleteFileOperation)operation; + + String deleteName = d.getName(); + + /** + * For Mac 2011 powerpoint files - add an extra check based on the filename + * e.g FileA1 - delete FileA. + */ + if(checkFilename) + { + int i = deleteName.lastIndexOf('.'); + + if(i > 0 && deleteName.substring(0, i).equalsIgnoreCase(createName.substring(0,i))) + { + logger.debug("check filenames - does match"); + } + else + { + if(logger.isDebugEnabled()) + { + logger.debug("check filename patterns do not match - Ignore" + createName + deleteName); + } + return null; + } + } + + if(logger.isDebugEnabled()) + { + logger.debug("got a delete : replace with rename createName:" + createName + "deleteName:" + deleteName); + } + + String tempName = ".shuffle" + d.getName(); + + deletes.put(d.getName(), tempName); + + String[] paths = FileName.splitPath(d.getPath()); + String currentFolder = paths[0]; + + RenameFileCommand r1 = new RenameFileCommand(d.getName(), tempName, d.getRootNodeRef(), d.getPath(), currentFolder + "\\" + tempName); + + return r1; + } + + /** + * + */ + if(operation instanceof RenameFileOperation) + { + RenameFileOperation m = (RenameFileOperation)operation; + + String targetFile = m.getTo(); + + if(deletes.containsKey(targetFile)) + { + String tempName = deletes.get(targetFile); + + String[] paths = FileName.splitPath(m.getToPath()); + String currentFolder = paths[0]; + + /** + * This is where the scenario fires. + * a) Rename the temp file back to the targetFile + * b) Copy content from moved file + * c) Delete rather than move file + */ + if(logger.isDebugEnabled()) + { + logger.debug("scenario fires:" + createName); + } + ArrayList commands = new ArrayList(); + + RenameFileCommand r1 = new RenameFileCommand(tempName, targetFile, m.getRootNodeRef(), currentFolder + "\\" + tempName, m.getToPath()); + + CopyContentCommand copyContent = new CopyContentCommand(m.getFrom(), targetFile, m.getRootNodeRef(), m.getFromPath(), m.getToPath()); + + DeleteFileCommand d1 = new DeleteFileCommand(m.getFrom(), m.getRootNodeRef(), m.getFromPath()); + + commands.add(r1); + commands.add(copyContent); + commands.add(d1); + + logger.debug("Scenario complete"); + isComplete = true; + + return new CompoundCommand(commands); + } + } + + } + + return null; + } + + @Override + public boolean isComplete() + { + return isComplete; + } + + @Override + public Ranking getRanking() + { + return ranking; + } + + public void setRanking(Ranking ranking) + { + this.ranking = ranking; + } + + public String toString() + { + return "ScenarioShuffleInstance: createName:" + createName; + } + + public void setTimeout(long timeout) + { + this.timeout = timeout; + } + + public long getTimeout() + { + return timeout; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioCreateShuffleInstance.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioCreateShuffleInstance.java index b782a74c4a..1428a2ec96 100644 --- a/source/java/org/alfresco/filesys/repo/rules/ScenarioCreateShuffleInstance.java +++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioCreateShuffleInstance.java @@ -72,7 +72,7 @@ public class ScenarioCreateShuffleInstance implements ScenarioInstance /** * Timeout in ms. Default 30 seconds. */ - private long timeout = 30000; + private long timeout = 60000; private boolean isComplete; @@ -88,24 +88,6 @@ public class ScenarioCreateShuffleInstance implements ScenarioInstance public Command evaluate(Operation operation) { -// /** -// * Anti-pattern for all states - delete the file we are -// * shuffling -// */ -// if(createName != null) -// { -// if(operation instanceof DeleteFileOperation) -// { -// DeleteFileOperation d = (DeleteFileOperation)operation; -// if(d.getName().equals(createName)) -// { -// logger.debug("Anti-pattern : Shuffle file deleted"); -// isComplete = true; -// return null; -// } -// } -// } - /** * Anti-pattern : timeout */ @@ -114,7 +96,30 @@ public class ScenarioCreateShuffleInstance implements ScenarioInstance { if(logger.isDebugEnabled()) { - logger.debug("Instance timed out"); + logger.debug("Instance timed out createName:" + createName); + isComplete = true; + return null; + } + } + + /** + * Anti-pattern for all states - delete the file we are + * shuffling + */ + if(createName != null) + { + if(operation instanceof DeleteFileOperation) + { + DeleteFileOperation d = (DeleteFileOperation)operation; + if(d.getName().equals(createName)) + { + if(logger.isDebugEnabled()) + { + logger.debug("Anti-pattern : Shuffle file deleted createName:" + createName); + } + isComplete = true; + return null; + } } } @@ -229,11 +234,11 @@ public class ScenarioCreateShuffleInstance implements ScenarioInstance DeleteFileOperation d = (DeleteFileOperation)operation; if(d.getName().equals(move2)) { - logger.debug("Scenario complete"); + if(logger.isDebugEnabled()) + { + logger.debug("Scenario complete createName:" + createName); + } isComplete = true; -// ArrayList commands = new ArrayList(); -// // missing stuff here -// return new CompoundCommand(commands); } } @@ -262,7 +267,7 @@ public class ScenarioCreateShuffleInstance implements ScenarioInstance public String toString() { - return "ScenarioShuffleInstance:" + createName; + return "ScenarioShuffleInstance: createName:" + createName; } public void setTimeout(long timeout) diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioDoubleRenameShuffleInstance.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioDoubleRenameShuffleInstance.java index f79f3a341e..31fe2f3213 100644 --- a/source/java/org/alfresco/filesys/repo/rules/ScenarioDoubleRenameShuffleInstance.java +++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioDoubleRenameShuffleInstance.java @@ -95,6 +95,7 @@ public class ScenarioDoubleRenameShuffleInstance implements ScenarioInstance if(logger.isDebugEnabled()) { logger.debug("Instance timed out"); + } } diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioLockedDeleteShuffleInstance.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioLockedDeleteShuffleInstance.java index d83ad8d0c3..8d0636a801 100644 --- a/source/java/org/alfresco/filesys/repo/rules/ScenarioLockedDeleteShuffleInstance.java +++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioLockedDeleteShuffleInstance.java @@ -79,11 +79,10 @@ public class ScenarioLockedDeleteShuffleInstance implements ScenarioInstance private Ranking ranking; - /** * Timeout in ms. Default 30 seconds. */ - private long timeout = 30000; + private long timeout = 60000; private boolean isComplete; @@ -127,7 +126,9 @@ public class ScenarioLockedDeleteShuffleInstance implements ScenarioInstance { if(logger.isDebugEnabled()) { - logger.debug("Instance timed out"); + logger.debug("Instance timed out lockName:" + lockName); + isComplete = true; + return null; } } @@ -212,7 +213,8 @@ public class ScenarioLockedDeleteShuffleInstance implements ScenarioInstance * a) Rename the temp file back to the targetFile * b) Copy content from moved file * c) Delete rather than move file - */ + */ + logger.debug("scenario fires"); ArrayList commands = new ArrayList(); RenameFileCommand r1 = new RenameFileCommand(tempName, targetFile, m.getRootNodeRef(), currentFolder + "\\" + tempName, m.getToPath()); @@ -228,17 +230,8 @@ public class ScenarioLockedDeleteShuffleInstance implements ScenarioInstance logger.debug("Scenario complete"); isComplete = true; - return new CompoundCommand(commands); - + return new CompoundCommand(commands); } - - //TODO - Need to consider error cases and "overlap" - -// if(logger.isDebugEnabled()) -// { -// logger.debug("entering MOVED state: " + lockName); -// } -// internalState = InternalState.MOVED; } diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioOpenFile.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioOpenFile.java index ce3e0bd571..1e43724699 100644 --- a/source/java/org/alfresco/filesys/repo/rules/ScenarioOpenFile.java +++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioOpenFile.java @@ -169,7 +169,7 @@ public class ScenarioOpenFile implements Scenario ScenarioOpenFileInstance i = (ScenarioOpenFileInstance)instance; if(i.getName() != null && name != null) { - if(i.getName().equalsIgnoreCase(name)); + if(i.getName().equalsIgnoreCase(name)) { return true; } diff --git a/source/java/org/alfresco/repo/avm/AVMLockingAwareService.java b/source/java/org/alfresco/repo/avm/AVMLockingAwareService.java index a9bb46e692..13e1b2e0d6 100644 --- a/source/java/org/alfresco/repo/avm/AVMLockingAwareService.java +++ b/source/java/org/alfresco/repo/avm/AVMLockingAwareService.java @@ -31,6 +31,7 @@ import org.alfresco.repo.avm.util.AVMUtil; import org.alfresco.repo.domain.PropertyValue; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.permissions.AccessDeniedException; +import org.alfresco.service.cmr.avm.AVMExistsException; import org.alfresco.service.cmr.avm.AVMNodeDescriptor; import org.alfresco.service.cmr.avm.AVMService; import org.alfresco.service.cmr.avm.AVMStoreDescriptor; @@ -64,6 +65,8 @@ public class AVMLockingAwareService implements AVMService, ApplicationContextAwa public static final String STORE_WORKFLOW = "workflow"; + public static final String STORE_PREVIEW = "preview"; + private AVMService fService; private AVMLockingService fLockingService; @@ -668,14 +671,27 @@ 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; + + // ALF-11440 PM 18-Dec-2011: + // 1. Managers may edit any unlocked file - it becomes locked. + // 2. Managers may edit any locked file in any sandbox in which it is locked + // but not in sandboxes where it is unlocked. + // ALF-8787 and ALF-12766 are consistent with 2. + // A Manager should only be able to create a file in a sandbox + // if it is NOT locked somewhere else. switch (lockState) { case LOCK_NOT_OWNER: String lockOwner = fLockingService.getLockOwner(webProject, storePath[1]); + if ((wpService.isContentManager(wpStoreId, userName)) && + (avmStore.equals(wpStoreId + STORE_SEPARATOR + lockOwner) || + avmStore.equals(wpStoreId + STORE_SEPARATOR + lockOwner + + STORE_SEPARATOR + STORE_PREVIEW))) + { + // Handle as if LOCK_OWNER + break; + } throw new AVMLockingException("avmlockservice.locked", path, lockOwner); case NO_LOCK: Map lockAttributes = Collections.singletonMap(WCMUtil.LOCK_KEY_STORE_NAME, avmStore); diff --git a/source/java/org/alfresco/repo/avm/AVMSyncServiceImpl.java b/source/java/org/alfresco/repo/avm/AVMSyncServiceImpl.java index 1e9c0e7452..375c3ae2e9 100644 --- a/source/java/org/alfresco/repo/avm/AVMSyncServiceImpl.java +++ b/source/java/org/alfresco/repo/avm/AVMSyncServiceImpl.java @@ -232,7 +232,7 @@ public class AVMSyncServiceImpl implements AVMSyncService dirDiffCode)); // Also add all child items if necessary and any exists - if (expandDirs) + if (expandDirs && srcDesc.isDirectory()) { addNewChildrenIfAny(srcVersion, srcDesc, dstVersion, AVMNodeConverter.ExtendAVMPath(dstPath, dstDesc.getName()), result); } @@ -283,7 +283,7 @@ public class AVMSyncServiceImpl implements AVMSyncService AVMDifference.NEWER)); // Also add all child items if necessary and any exists - if (expandDirs) + if (expandDirs && srcChild.isDirectory()) { addNewChildrenIfAny(srcVersion, srcChild, dstVersion, dstChildPath, result); } diff --git a/source/java/org/alfresco/repo/content/AbstractContentReader.java b/source/java/org/alfresco/repo/content/AbstractContentReader.java index 5e4ae7432e..5e7e2688c0 100644 --- a/source/java/org/alfresco/repo/content/AbstractContentReader.java +++ b/source/java/org/alfresco/repo/content/AbstractContentReader.java @@ -69,7 +69,7 @@ import sun.nio.ch.ChannelInputStream; public abstract class AbstractContentReader extends AbstractContentAccessor implements ContentReader { private static final Log logger = LogFactory.getLog(AbstractContentReader.class); - private static final Timer timer = new Timer(); + private static final Timer timer = new Timer(true); private List listeners; private ReadableByteChannel channel; diff --git a/source/java/org/alfresco/repo/content/transform/PdfToImageContentTransformer.java b/source/java/org/alfresco/repo/content/transform/PdfToImageContentTransformer.java index 6acb28be1e..1eedb7656f 100644 --- a/source/java/org/alfresco/repo/content/transform/PdfToImageContentTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/PdfToImageContentTransformer.java @@ -93,6 +93,11 @@ public class PdfToImageContentTransformer extends AbstractContentTransformer2 } PDFPage page = pdffile.getPage(0, true); + if (page == null) + { + throw new AlfrescoRuntimeException("Unable to create image from pdf file."+ + "A PDFRender error took place which should have been sent to stdout."); + } //get the width and height for the doc at the default zoom int width=(int)page.getBBox().getWidth(); diff --git a/source/java/org/alfresco/repo/content/transform/magick/ImageMagickContentTransformerWorker.java b/source/java/org/alfresco/repo/content/transform/magick/ImageMagickContentTransformerWorker.java index 9f3ffda96f..f277ed5b72 100644 --- a/source/java/org/alfresco/repo/content/transform/magick/ImageMagickContentTransformerWorker.java +++ b/source/java/org/alfresco/repo/content/transform/magick/ImageMagickContentTransformerWorker.java @@ -154,6 +154,10 @@ public class ImageMagickContentTransformerWorker extends AbstractImageMagickCont ImageCropOptions cropOptions = imageOptions.getCropOptions(); ImageResizeOptions resizeOptions = imageOptions.getResizeOptions(); String commandOptions = imageOptions.getCommandOptions(); + if (commandOptions == null) + { + commandOptions = ""; + } if (imageOptions.isAutoOrient()) { commandOptions = commandOptions + " -auto-orient"; diff --git a/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java b/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java index 7ac064e6e4..cc758ce17a 100644 --- a/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java @@ -57,10 +57,10 @@ 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.TransactionAwareSingleton; -import org.alfresco.repo.transaction.TransactionListenerAdapter; import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.repo.transaction.TransactionAwareSingleton; +import org.alfresco.repo.transaction.TransactionListenerAdapter; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.dictionary.InvalidTypeException; @@ -74,20 +74,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.ValueProtectingMap; -import org.alfresco.util.EqualsHelper.MapValueComparison; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.dao.ConcurrencyFailureException; @@ -957,12 +957,8 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO 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; + // The node is actually deleted as the cache said. + throw new InvalidNodeRefException(nodeRef); } else { @@ -4248,7 +4244,7 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO { return selectTxnsUnused(minTxnId, maxCommitTime, count); } - + public void purgeTxn(Long txnId) { deleteTransaction(txnId); @@ -4268,6 +4264,24 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO return (time == null ? LONG_ZERO : time); } + public Long getMinTxnId() + { + Long id = selectMinTxnId(); + return (id == null ? LONG_ZERO : id); + } + + public Long getMinUnusedTxnCommitTime() + { + Long id = selectMinUnusedTxnCommitTime(); + return (id == null ? LONG_ZERO : id); + } + + public Long getMaxTxnId() + { + Long id = selectMaxTxnId(); + return (id == null ? LONG_ZERO : id); + } + /* * Abstract methods for underlying CRUD */ @@ -4438,4 +4452,7 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO protected abstract List selectTxnsUnused(Long minTxnId, Long maxCommitTime, Integer count); protected abstract Long selectMinTxnCommitTime(); protected abstract Long selectMaxTxnCommitTime(); + protected abstract Long selectMinTxnId(); + protected abstract Long selectMaxTxnId(); + protected abstract Long selectMinUnusedTxnCommitTime(); } diff --git a/source/java/org/alfresco/repo/domain/node/NodeDAO.java b/source/java/org/alfresco/repo/domain/node/NodeDAO.java index 003f674401..bd0043725d 100644 --- a/source/java/org/alfresco/repo/domain/node/NodeDAO.java +++ b/source/java/org/alfresco/repo/domain/node/NodeDAO.java @@ -710,6 +710,16 @@ public interface NodeDAO extends NodeBulkLoader public List getTxnsUnused(Long minTxnId, long maxCommitTime, int count); + /** + * Remove unused transactions from commit time 'fromCommitTime' to commit time 'toCommitTime' + * + * @param fromCommitTime delete unused transactions from commit time + * @param toCommitTime delete unused transactions to commit time + * + * @return + */ + public int deleteTxnsUnused(long fromCommitTime, long toCommitTime); + public void purgeTxn(Long txnId); /** @@ -722,6 +732,22 @@ public interface NodeDAO extends NodeBulkLoader */ public Long getMaxTxnCommitTime(); + /** + * @return Returns the minimum id or 0 if there are no transactions + */ + public Long getMinTxnId(); + + /** + * + * @return the commit time of the oldest unused transaction + */ + public Long getMinUnusedTxnCommitTime(); + + /** + * @return Returns the maximum id or 0 if there are no transactions + */ + public Long getMaxTxnId(); + /** * Select children by property values */ 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 b92e3223e8..f381c2bcc7 100644 --- a/source/java/org/alfresco/repo/domain/node/ibatis/NodeDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/node/ibatis/NodeDAOImpl.java @@ -142,8 +142,12 @@ public class NodeDAOImpl extends AbstractNodeDAOImpl private static final String SELECT_TXN_COUNT = "alfresco.node.select_TxnCount"; private static final String SELECT_TXN_NODE_COUNT = "alfresco.node.select_TxnNodeCount"; private static final String SELECT_TXNS_UNUSED = "alfresco.node.select_TxnsUnused"; + private static final String DELETE_TXNS_UNUSED = "alfresco.node.delete_Txns_Unused"; private static final String SELECT_TXN_MIN_COMMIT_TIME = "alfresco.node.select_TxnMinCommitTime"; private static final String SELECT_TXN_MAX_COMMIT_TIME = "alfresco.node.select_TxnMaxCommitTime"; + private static final String SELECT_TXN_MIN_ID = "alfresco.node.select_TxnMinId"; + private static final String SELECT_TXN_MAX_ID = "alfresco.node.select_TxnMaxId"; + private static final String SELECT_TXN_UNUSED_MIN_COMMIT_TIME = "alfresco.node.select_TxnMinUnusedCommitTime"; private QNameDAO qnameDAO; private DictionaryService dictionaryService; @@ -1605,6 +1609,16 @@ public class NodeDAOImpl extends AbstractNodeDAOImpl } } + @Override + public int deleteTxnsUnused(long fromCommitTime, long toCommitTime) + { + TransactionQueryEntity txnQuery = new TransactionQueryEntity(); + txnQuery.setMinCommitTime(fromCommitTime); + txnQuery.setMaxCommitTime(toCommitTime); + int numDeleted = template.delete(DELETE_TXNS_UNUSED, txnQuery); + return numDeleted; + } + @Override protected Long selectMinTxnCommitTime() { @@ -1616,6 +1630,24 @@ public class NodeDAOImpl extends AbstractNodeDAOImpl { return (Long) template.selectOne(SELECT_TXN_MAX_COMMIT_TIME); } + + @Override + protected Long selectMinTxnId() + { + return (Long) template.selectOne(SELECT_TXN_MIN_ID); + } + + @Override + protected Long selectMinUnusedTxnCommitTime() + { + return (Long) template.selectOne(SELECT_TXN_UNUSED_MIN_COMMIT_TIME); + } + + @Override + protected Long selectMaxTxnId() + { + return (Long) template.selectOne(SELECT_TXN_MAX_ID); + } @Override public List selectProperties(Collection propertyDefs) diff --git a/source/java/org/alfresco/repo/domain/permissions/AbstractAclCrudDAOImpl.java b/source/java/org/alfresco/repo/domain/permissions/AbstractAclCrudDAOImpl.java index 35822aac7d..1ea9317d2b 100644 --- a/source/java/org/alfresco/repo/domain/permissions/AbstractAclCrudDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/permissions/AbstractAclCrudDAOImpl.java @@ -1021,4 +1021,38 @@ public abstract class AbstractAclCrudDAOImpl implements AclCrudDAO protected abstract long createAuthorityAliasEntity(AuthorityAliasEntity entity); protected abstract int deleteAuthorityAliasEntity(long id); + + + /* (non-Javadoc) + * @see org.alfresco.repo.domain.permissions.AclCrudDAO#getMaxChangeSetCommitTime() + */ + @Override + public Long getMaxChangeSetCommitTime() + { + Long time = selectMaxChangeSetCommitTime(); + return (time == null ? 0L : time); + } + + + /** + * @return + */ + protected abstract Long selectMaxChangeSetCommitTime(); + + + /* (non-Javadoc) + * @see org.alfresco.repo.domain.permissions.AclCrudDAO#getMaxChangeSetIdByCommitTime(long) + */ + @Override + public Long getMaxChangeSetIdByCommitTime(long maxCommitTime) + { + Long id = selectMaxChangeSetIdBeforeCommitTime(maxCommitTime); + return (id == null ? 0L : id); + } + + /** + * @param maxCommitTime + * @return + */ + protected abstract Long selectMaxChangeSetIdBeforeCommitTime(long maxCommitTime); } diff --git a/source/java/org/alfresco/repo/domain/permissions/AclCrudDAO.java b/source/java/org/alfresco/repo/domain/permissions/AclCrudDAO.java index d57018ce5b..b68a6ab7bc 100644 --- a/source/java/org/alfresco/repo/domain/permissions/AclCrudDAO.java +++ b/source/java/org/alfresco/repo/domain/permissions/AclCrudDAO.java @@ -112,6 +112,15 @@ public interface AclCrudDAO public Authority getOrCreateAuthority(String authorityName); public void renameAuthority(String authorityNameBefore, String authorityAfter); public void deleteAuthority(long authorityEntityId); + /** + * @return + */ + public Long getMaxChangeSetCommitTime(); + /** + * @param maxCommitTime + * @return + */ + public Long getMaxChangeSetIdByCommitTime(long maxCommitTime); // AceContext (NOTE: currently unused - intended for possible future enhancement) // AuthorityAlias (NOTE: currently unused - intended for possible future enhancement) diff --git a/source/java/org/alfresco/repo/domain/permissions/AclDAO.java b/source/java/org/alfresco/repo/domain/permissions/AclDAO.java index 52ff222c69..b455c7eafc 100644 --- a/source/java/org/alfresco/repo/domain/permissions/AclDAO.java +++ b/source/java/org/alfresco/repo/domain/permissions/AclDAO.java @@ -160,4 +160,15 @@ public interface AclDAO * @param aclId */ public void fixSharedAcl(Long shared, Long defining); + + /** + * @return + */ + public Long getMaxChangeSetCommitTime(); + + /** + * @param maxCommitTime + * @return + */ + public Long getMaxChangeSetIdByCommitTime(long maxCommitTime); } diff --git a/source/java/org/alfresco/repo/domain/permissions/AclDAOImpl.java b/source/java/org/alfresco/repo/domain/permissions/AclDAOImpl.java index 4d7d9553e3..13b2fecd2e 100644 --- a/source/java/org/alfresco/repo/domain/permissions/AclDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/permissions/AclDAOImpl.java @@ -72,6 +72,8 @@ public class AclDAOImpl implements AclDAO private TenantService tenantService; private SimpleCache aclCache; private SimpleCache> readersCache; + + private SimpleCache> readersDeniedCache; private enum WriteMode { @@ -142,6 +144,14 @@ public class AclDAOImpl implements AclDAO { this.readersCache = readersCache; } + + /** + * @param readersDeniedCache the readersDeniedCache to set + */ + public void setReadersDeniedCache(SimpleCache> readersDeniedCache) + { + this.readersDeniedCache = readersDeniedCache; + } /** * {@inheritDoc} @@ -434,6 +444,7 @@ public class AclDAOImpl implements AclDAO { aclCache.remove(id); readersCache.remove(id); + readersDeniedCache.remove(id); return new AclChangeImpl(id, id, acl.getAclType(), acl.getAclType()); } @@ -484,6 +495,7 @@ public class AclDAOImpl implements AclDAO aclCrudDAO.updateAcl(acl); aclCache.remove(id); readersCache.remove(id); + readersDeniedCache.remove(id); return new AclChangeImpl(id, id, acl.getAclType(), acl.getAclType()); } else if ((acl.getAclChangeSetId() == getCurrentChangeSetId()) && (!requiresVersion) && (!acl.getRequiresVersion())) @@ -523,6 +535,7 @@ public class AclDAOImpl implements AclDAO aclCrudDAO.updateAcl(acl); aclCache.remove(id); readersCache.remove(id); + readersDeniedCache.remove(id); return new AclChangeImpl(id, id, acl.getAclType(), acl.getAclType()); } else @@ -612,6 +625,7 @@ public class AclDAOImpl implements AclDAO aclCrudDAO.updateAcl(acl); aclCache.remove(id); readersCache.remove(id); + readersDeniedCache.remove(id); return new AclChangeImpl(id, created, acl.getAclType(), newAcl.getAclType()); } } @@ -803,6 +817,7 @@ public class AclDAOImpl implements AclDAO { aclCache.remove(aclId); readersCache.remove(aclId); + readersDeniedCache.remove(aclId); Acl list = aclCrudDAO.getAcl(aclId); acls.add(new AclChangeImpl(aclId, aclId, list.getAclType(), list.getAclType())); @@ -860,6 +875,7 @@ public class AclDAOImpl implements AclDAO aclCache.remove(aclId); readersCache.remove(aclId); + readersDeniedCache.remove(aclId); } if (dbAcl.getAclType() == ACLType.SHARED) { @@ -878,6 +894,7 @@ public class AclDAOImpl implements AclDAO aclCache.remove(aclId); readersCache.remove(aclId); + readersDeniedCache.remove(aclId); } } else @@ -992,6 +1009,7 @@ public class AclDAOImpl implements AclDAO // remove the deleted acl from the cache aclCache.remove(id); readersCache.remove(id); + readersDeniedCache.remove(id); acls.add(new AclChangeImpl(id, null, acl.getAclType(), null)); return acls; } @@ -1337,6 +1355,7 @@ public class AclDAOImpl implements AclDAO aclCrudDAO.updateAcl(acl); aclCache.remove(id); readersCache.remove(id); + readersDeniedCache.remove(id); changes.add(new AclChangeImpl(id, id, acl.getAclType(), acl.getAclType())); return changes; case SHARED: @@ -1387,6 +1406,7 @@ public class AclDAOImpl implements AclDAO aclCrudDAO.updateAcl(acl); aclCache.remove(id); readersCache.remove(id); + readersDeniedCache.remove(id); changes.add(new AclChangeImpl(id, id, acl.getAclType(), acl.getAclType())); return changes; case SHARED: @@ -1422,6 +1442,7 @@ public class AclDAOImpl implements AclDAO aclCrudDAO.updateAcl(aclToCopy); aclCache.remove(toCopy); readersCache.remove(toCopy); + readersDeniedCache.remove(toCopy); inheritedId = getInheritedAccessControlList(toCopy); if ((inheritedId != null) && (!inheritedId.equals(toCopy))) { @@ -1431,6 +1452,7 @@ public class AclDAOImpl implements AclDAO aclCrudDAO.updateAcl(inheritedAcl); aclCache.remove(inheritedId); readersCache.remove(inheritedId); + readersDeniedCache.remove(inheritedId); } return toCopy; case REDIRECT: @@ -1908,4 +1930,22 @@ public class AclDAOImpl implements AclDAO List changes = new ArrayList(); getWritable(shared, defining, null, null, defining, true, changes, WriteMode.CHANGE_INHERITED); } + + /* (non-Javadoc) + * @see org.alfresco.repo.domain.permissions.AclDAO#getMaxChangeSetCommitTime() + */ + @Override + public Long getMaxChangeSetCommitTime() + { + return aclCrudDAO.getMaxChangeSetCommitTime(); + } + + /* (non-Javadoc) + * @see org.alfresco.repo.domain.permissions.AclDAO#getMaxChangeSetIdByCommitTime(long) + */ + @Override + public Long getMaxChangeSetIdByCommitTime(long maxCommitTime) + { + return aclCrudDAO.getMaxChangeSetIdByCommitTime(maxCommitTime); + } } diff --git a/source/java/org/alfresco/repo/domain/permissions/ibatis/AclCrudDAOImpl.java b/source/java/org/alfresco/repo/domain/permissions/ibatis/AclCrudDAOImpl.java index f8760f1ab0..9ee24b737a 100644 --- a/source/java/org/alfresco/repo/domain/permissions/ibatis/AclCrudDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/permissions/ibatis/AclCrudDAOImpl.java @@ -35,6 +35,7 @@ import org.alfresco.repo.domain.permissions.PermissionEntity; import org.alfresco.repo.security.permissions.ACEType; import org.apache.ibatis.session.RowBounds; import org.mybatis.spring.SqlSessionTemplate; +import org.springframework.util.Assert; /** * iBatis-specific implementation of the ACL Crud DAO. @@ -91,6 +92,9 @@ public class AclCrudDAOImpl extends AbstractAclCrudDAOImpl private static final String INSERT_AUTHORITY_ALIAS = "alfresco.permissions.insert.insert_AuthorityAlias"; private static final String DELETE_AUTHORITY_ALIAS = "alfresco.permissions.delete_AuthorityAlias"; + private static final String SELECT_CHANGE_SET_LAST = "alfresco.permissions.select_ChangeSetLast"; + private static final String SELECT_CHANGE_SET_MAX_COMMIT_TIME = "alfresco.permissions.select_ChangeSetMaxCommitTime";; + private SqlSessionTemplate template; @@ -472,4 +476,40 @@ public class AclCrudDAOImpl extends AbstractAclCrudDAOImpl return template.delete(DELETE_AUTHORITY_ALIAS, params); } + + + /* (non-Javadoc) + * @see org.alfresco.repo.domain.permissions.AbstractAclCrudDAOImpl#selectMaxChangeSetCommitTime() + */ + @Override + protected Long selectMaxChangeSetCommitTime() + { + return (Long) template.selectOne(SELECT_CHANGE_SET_MAX_COMMIT_TIME); + } + + + /* (non-Javadoc) + * @see org.alfresco.repo.domain.permissions.AbstractAclCrudDAOImpl#selectMaxChangeSetIdBeforeCommitTime(long) + */ + @Override + protected Long selectMaxChangeSetIdBeforeCommitTime(long maxCommitTime) + { + Assert.notNull(maxCommitTime, "maxCommitTime"); + + Map params = new HashMap(1); + params.put("commit_time_ms", maxCommitTime); + + List sets = (List) template.selectList(SELECT_CHANGE_SET_LAST, params, new RowBounds(0, 1)); + if (sets.size() > 0) + { + return sets.get(0); + } + else + { + return null; + } + } + + + } diff --git a/source/java/org/alfresco/repo/invitation/InvitationServiceImpl.java b/source/java/org/alfresco/repo/invitation/InvitationServiceImpl.java index 2c346cef8d..4229a6c50a 100644 --- a/source/java/org/alfresco/repo/invitation/InvitationServiceImpl.java +++ b/source/java/org/alfresco/repo/invitation/InvitationServiceImpl.java @@ -32,13 +32,16 @@ import java.util.Set; import org.alfresco.model.ContentModel; import org.alfresco.repo.admin.SysAdminParams; import org.alfresco.repo.node.NodeServicePolicies; +import org.alfresco.repo.node.NodeServicePolicies.BeforeDeleteNodePolicy; 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.AuthenticationException; 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.transaction.TransactionalResourceHelper; import org.alfresco.repo.workflow.CancelWorkflowActionExecuter; import org.alfresco.repo.workflow.WorkflowModel; import org.alfresco.repo.workflow.activiti.ActivitiConstants; @@ -151,6 +154,7 @@ public class InvitationServiceImpl implements InvitationService, NodeServicePoli // this.policyComponent.bindClassBehaviour(QName.createQName(NamespaceService.ALFRESCO_URI, "beforeDeleteNode"), SiteModel.TYPE_SITE, new JavaBehaviour(this, "beforeDeleteNode")); + this.policyComponent.bindClassBehaviour(BeforeDeleteNodePolicy.QNAME, ContentModel.TYPE_PERSON, new JavaBehaviour(this, "beforeDeleteNode")); } /** @@ -362,23 +366,27 @@ public class InvitationServiceImpl implements InvitationService, NodeServicePoli private void endInvitation(WorkflowTask startTask, String transition, Map properties, QName... taskTypes ) { - List tasks = workflowService.getTasksForWorkflowPath(startTask.getPath().getId()); - if(tasks.size()==1) - { - WorkflowTask task = tasks.get(0); - if(taskTypeMatches(task, taskTypes)) + // Deleting a person can cancel their invitations. Cancelling invitations can delete inactive persons! So prevent infinite looping here + if (TransactionalResourceHelper.getSet(getClass().getName()).add(startTask.getPath().getInstance().getId())) + { + List tasks = workflowService.getTasksForWorkflowPath(startTask.getPath().getId()); + if(tasks.size()==1) { - if(properties != null) + WorkflowTask task = tasks.get(0); + if(taskTypeMatches(task, taskTypes)) { - workflowService.updateTask(task.getId(), properties, null, null); + if(properties != null) + { + workflowService.updateTask(task.getId(), properties, null, null); + } + workflowService.endTask(task.getId(), transition); + return; } - workflowService.endTask(task.getId(), transition); - return; } + // Throw exception if the task not found. + Object objs[] = { startTask.getPath().getInstance().getId() }; + throw new InvitationExceptionUserError("invitation.invite.already_finished", objs); } - // Throw exception if the task not found. - Object objs[] = { startTask.getPath().getInstance().getId() }; - throw new InvitationExceptionUserError("invitation.invite.already_finished", objs); } /** @@ -476,11 +484,19 @@ public class InvitationServiceImpl implements InvitationService, NodeServicePoli { ModeratedInvitation invitation = getModeratedInvitation(startTask); String currentUserName = this.authenticationService.getCurrentUserName(); - if (false == currentUserName.equals(invitation.getInviteeUserName())) + if (!AuthenticationUtil.isRunAsUserTheSystemUser()) { - checkManagerRole(currentUserName, invitation.getResourceType(), invitation.getResourceName()); + if (false == currentUserName.equals(invitation.getInviteeUserName())) + { + checkManagerRole(currentUserName, invitation.getResourceType(), invitation.getResourceName()); + } + } + // Only proceed with the cancel if the site still exists (the site may have been deleted and invitations may be + // getting cancelled in the background) + if (this.siteService.getSite(invitation.getResourceName()) != null) + { + workflowService.cancelWorkflow(invitation.getInviteId()); } - workflowService.cancelWorkflow(invitation.getInviteId()); return invitation; } @@ -488,13 +504,21 @@ public class InvitationServiceImpl implements InvitationService, NodeServicePoli { NominatedInvitation invitation = getNominatedInvitation(startTask); String currentUserName = this.authenticationService.getCurrentUserName(); - if (false == currentUserName.equals(invitation.getInviterUserName())) + if (!AuthenticationUtil.isRunAsUserTheSystemUser()) { - checkManagerRole(currentUserName, invitation.getResourceType(), invitation.getResourceName()); + if (false == currentUserName.equals(invitation.getInviterUserName())) + { + checkManagerRole(currentUserName, invitation.getResourceType(), invitation.getResourceName()); + } + } + // Only proceed with the cancel if the site still exists (the site may have been deleted and invitations may be + // getting cancelled in the background) + if (this.siteService.getSite(invitation.getResourceName()) != null) + { + endInvitation(startTask, WorkflowModelNominatedInvitation.WF_TRANSITION_CANCEL, null, + WorkflowModelNominatedInvitation.WF_TASK_INVITE_PENDING, + WorkflowModelNominatedInvitation.WF_TASK_ACTIVIT_INVITE_PENDING); } - endInvitation(startTask, - WorkflowModelNominatedInvitation.WF_TRANSITION_CANCEL, null, - WorkflowModelNominatedInvitation.WF_TASK_INVITE_PENDING, WorkflowModelNominatedInvitation.WF_TASK_ACTIVIT_INVITE_PENDING); return invitation; } @@ -1468,11 +1492,26 @@ public class InvitationServiceImpl implements InvitationService, NodeServicePoli } } } + else if (dictionaryService.isSubClass(type, ContentModel.TYPE_PERSON)) + { + // this is a user being deleted. + String userName = (String) nodeService.getProperty(siteRef, ContentModel.PROP_USERNAME); + invalidateTasksByUser(userName); + } return null; } }, AuthenticationUtil.SYSTEM_USER_NAME); } + private void invalidateTasksByUser(String userName) throws AuthenticationException + { + List listForInvitee = listPendingInvitationsForInvitee(userName); + for (Invitation inv : listForInvitee) + { + cancel(inv.getInviteId()); + } + } + /** * Generates a description for the workflow * diff --git a/source/java/org/alfresco/repo/invitation/InviteHelper.java b/source/java/org/alfresco/repo/invitation/InviteHelper.java index b87dad16c5..af0a34cee5 100644 --- a/source/java/org/alfresco/repo/invitation/InviteHelper.java +++ b/source/java/org/alfresco/repo/invitation/InviteHelper.java @@ -39,6 +39,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.alfresco.model.ContentModel; import org.alfresco.repo.action.executer.MailActionExecuter; import org.alfresco.repo.i18n.MessageService; import org.alfresco.repo.invitation.site.InviteInfo; @@ -57,6 +58,7 @@ import org.alfresco.service.cmr.invitation.Invitation; import org.alfresco.service.cmr.invitation.InvitationExceptionForbidden; import org.alfresco.service.cmr.invitation.InvitationService; 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.security.MutableAuthenticationService; import org.alfresco.service.cmr.security.PersonService; @@ -112,6 +114,7 @@ public class InviteHelper implements InitializingBean private SiteService siteService; private TemplateService templateService; private WorkflowService workflowService; + private NodeService nodeService; private InviteSender inviteSender; @@ -125,6 +128,7 @@ public class InviteHelper implements InitializingBean this.siteService = serviceRegistry.getSiteService(); this.templateService = serviceRegistry.getTemplateService(); this.workflowService = serviceRegistry.getWorkflowService(); + this.nodeService = serviceRegistry.getNodeService(); this.inviteSender = new InviteSender(serviceRegistry, repositoryHelper, messageService); } @@ -139,7 +143,7 @@ public class InviteHelper implements InitializingBean { public Void doWork() throws Exception { - if (false==authenticationService.getAuthenticationEnabled(invitee)) + if (authenticationService.isAuthenticationMutable(invitee)) { authenticationService.setAuthenticationEnabled(invitee, true); } @@ -282,7 +286,7 @@ public class InviteHelper implements InitializingBean // if invitee's user account is still disabled and there are no pending invites outstanding // for the invitee, then remove the account and delete the invitee's person node - if ((authenticationService.authenticationExists(inviteeUserName)) + if ((authenticationService.isAuthenticationMutable(inviteeUserName)) && (authenticationService.getAuthenticationEnabled(inviteeUserName) == false) && (invitesPending == false)) { @@ -309,14 +313,17 @@ public class InviteHelper implements InitializingBean String inviteeUserName = (String) executionVariables.get(wfVarInviteeUserName); String siteShortName = (String) executionVariables.get(wfVarResourceName); - String currentUserName = authenticationService.getCurrentUserName(); - String currentUserSiteRole = siteService.getMembersRole(siteShortName, currentUserName); - if (SiteModel.SITE_MANAGER.equals(currentUserSiteRole)== false) + if (!AuthenticationUtil.isRunAsUserTheSystemUser()) { - // The current user is not the site manager - String inviteId = (String) executionVariables.get(wfVarWorkflowInstanceId); - Object[] args = {currentUserName, inviteId, siteShortName}; - throw new InvitationExceptionForbidden(MSG_NOT_SITE_MANAGER, args); + String currentUserName = authenticationService.getCurrentUserName(); + String currentUserSiteRole = siteService.getMembersRole(siteShortName, currentUserName); + if (SiteModel.SITE_MANAGER.equals(currentUserSiteRole)== false) + { + // The current user is not the site manager + String inviteId = (String) executionVariables.get(wfVarWorkflowInstanceId); + Object[] args = {currentUserName, inviteId, siteShortName}; + throw new InvitationExceptionForbidden(MSG_NOT_SITE_MANAGER, args); + } } // Clean up invitee's user account and person node if they are not in use i.e. @@ -395,7 +402,7 @@ public class InviteHelper implements InitializingBean // Send Action emailAction = actionService.createAction("mail"); - emailAction.setParameterValue(MailActionExecuter.PARAM_TO, inviteeUserName); + emailAction.setParameterValue(MailActionExecuter.PARAM_TO, nodeService.getProperty(personService.getPerson(inviteeUserName), ContentModel.PROP_EMAIL)); emailAction.setParameterValue(MailActionExecuter.PARAM_FROM, reviewer); //TODO Localize this. emailAction.setParameterValue(MailActionExecuter.PARAM_SUBJECT, "Rejected invitation to web site:" + resourceName); diff --git a/source/java/org/alfresco/repo/jscript/ScriptNode.java b/source/java/org/alfresco/repo/jscript/ScriptNode.java index 4412fbb5bc..0866c46437 100644 --- a/source/java/org/alfresco/repo/jscript/ScriptNode.java +++ b/source/java/org/alfresco/repo/jscript/ScriptNode.java @@ -26,10 +26,12 @@ import java.io.OutputStream; import java.io.Reader; import java.io.Serializable; import java.nio.charset.Charset; +import java.text.Collator; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.HashSet; @@ -112,6 +114,7 @@ import org.mozilla.javascript.ScriptableObject; import org.mozilla.javascript.UniqueTag; import org.mozilla.javascript.Wrapper; import org.springframework.extensions.surf.util.Content; +import org.springframework.extensions.surf.util.I18NUtil; import org.springframework.extensions.surf.util.ParameterCheck; import org.springframework.extensions.surf.util.URLEncoder; @@ -425,6 +428,10 @@ public class ScriptNode implements Scopeable, NamespacePrefixResolverProvider // create our Node representation from the NodeRef children[i] = newInstance(childRefs.get(i).getChildRef(), this.services, this.scope); } + + // Do a locale-sensitive sort by name + sort(children); + this.children = Context.getCurrentContext().newArray(this.scope, children); this.hasChildren = (children.length != 0); } @@ -432,6 +439,21 @@ public class ScriptNode implements Scopeable, NamespacePrefixResolverProvider return this.children; } + /** + * Performs a locale-sensitive sort by name of a node array + * @param nodes the node array + */ + private static void sort(Object[] nodes) + { + final Collator col = Collator.getInstance(I18NUtil.getLocale()); + Arrays.sort(nodes, new Comparator(){ + @Override + public int compare(Object o1, Object o2) + { + return col.compare(((ScriptNode)o1).getName(), ((ScriptNode)o2).getName()); + }}); + } + /** * @return true if the Node has children */ diff --git a/source/java/org/alfresco/repo/jscript/Search.java b/source/java/org/alfresco/repo/jscript/Search.java index 31e2324b29..5f8a1e5018 100644 --- a/source/java/org/alfresco/repo/jscript/Search.java +++ b/source/java/org/alfresco/repo/jscript/Search.java @@ -46,12 +46,11 @@ import org.apache.commons.logging.LogFactory; import org.dom4j.Document; import org.dom4j.Element; import org.dom4j.io.SAXReader; +import org.jaxen.saxpath.base.XPathReader; import org.mozilla.javascript.Context; import org.mozilla.javascript.Scriptable; import org.springframework.extensions.surf.util.ParameterCheck; -import com.werken.saxpath.XPathReader; - /** * Search component for use by the ScriptService. *

diff --git a/source/java/org/alfresco/repo/model/Repository.java b/source/java/org/alfresco/repo/model/Repository.java index e827085324..181b874d83 100644 --- a/source/java/org/alfresco/repo/model/Repository.java +++ b/source/java/org/alfresco/repo/model/Repository.java @@ -263,9 +263,12 @@ public class Repository implements ApplicationContextAware, ApplicationListener, { NodeRef person = null; String currentUserName = AuthenticationUtil.getRunAsUser(); - if (personService.personExists(currentUserName)) + if (currentUserName != null) { - person = personService.getPerson(currentUserName); + if (personService.personExists(currentUserName)) + { + person = personService.getPerson(currentUserName); + } } return person; } diff --git a/source/java/org/alfresco/repo/model/filefolder/FilenameFilteringInterceptor.java b/source/java/org/alfresco/repo/model/filefolder/FilenameFilteringInterceptor.java index 983b9ea3dc..5af9dd1718 100644 --- a/source/java/org/alfresco/repo/model/filefolder/FilenameFilteringInterceptor.java +++ b/source/java/org/alfresco/repo/model/filefolder/FilenameFilteringInterceptor.java @@ -19,6 +19,7 @@ package org.alfresco.repo.model.filefolder; import java.util.Iterator; +import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.repo.model.filefolder.HiddenAspect.Visibility; import org.alfresco.repo.security.authentication.AuthenticationUtil; @@ -110,10 +111,13 @@ public class FilenameFilteringInterceptor implements MethodInterceptor this.permissionService = permissionService; } - private void checkTemporaryAspect(boolean isTemporary, FileInfo fileInfo) + private void checkTemporaryAspect(boolean isTemporary, FileInfo fileInfo) + { + checkTemporaryAspect(isTemporary, fileInfo.getNodeRef()); + } + + private void checkTemporaryAspect(boolean isTemporary, NodeRef nodeRef) { - NodeRef nodeRef = fileInfo.getNodeRef(); - if(isTemporary) { // it matched, so apply the temporary and hidden aspects @@ -121,7 +125,7 @@ public class FilenameFilteringInterceptor implements MethodInterceptor if (logger.isDebugEnabled()) { - logger.debug("Applied temporary marker: " + fileInfo); + logger.debug("Applied temporary marker: " + nodeRef); } } else @@ -135,7 +139,7 @@ public class FilenameFilteringInterceptor implements MethodInterceptor if (logger.isDebugEnabled()) { - logger.debug("Removed temporary marker: " + fileInfo); + logger.debug("Removed temporary marker: " + nodeRef); } } } @@ -243,17 +247,37 @@ public class FilenameFilteringInterceptor implements MethodInterceptor else { ret = invocation.proceed(); + FileInfoImpl fileInfo = (FileInfoImpl)ret; checkTemporaryAspect(temporaryFiles.isFiltered(filename), fileInfo); } } - else if (methodName.startsWith("rename") || - methodName.startsWith("move") || - methodName.startsWith("copy")) + else if (methodName.startsWith("move")) + { + Object[] args = invocation.getArguments(); + NodeRef sourceNodeRef = (NodeRef)args[0]; + String newName = (String)args[args.length -1]; + + if(newName != null) + { + // Name is changing + // check against all the regular expressions + checkTemporaryAspect(temporaryFiles.isFiltered(newName), sourceNodeRef); + if(getMode() == Mode.ENHANCED) + { + hiddenAspect.checkHidden(sourceNodeRef, true); + } + } + + // now do the move + ret = invocation.proceed(); + } + else if (methodName.startsWith("copy")) { ret = invocation.proceed(); + FileInfoImpl fileInfo = (FileInfoImpl) ret; String filename = fileInfo.getName(); @@ -269,6 +293,46 @@ public class FilenameFilteringInterceptor implements MethodInterceptor { hiddenAspect.checkHidden(fileInfo, true); } + /* + * TODO should these two calls be before the proceed? However its the same problem as create + * The node needs to be created before we can add aspects. + */ + } + else if (methodName.startsWith("rename")) + { + Object[] args = invocation.getArguments(); + + if(args != null && args.length == 2) + { + /** + * Expecting rename(NodeRef, newName) + */ + String newName = (String)args[1]; + NodeRef sourceNodeRef = (NodeRef)args[0]; + + if (logger.isDebugEnabled()) + { + logger.debug("Checking filename returned by " + methodName + ": " + newName); + } + + // check against all the regular expressions + checkTemporaryAspect(temporaryFiles.isFiltered(newName), sourceNodeRef); + if(getMode() == Mode.ENHANCED) + { + hiddenAspect.checkHidden(sourceNodeRef, true); + } + + ret = invocation.proceed(); + + return ret; + } + else + { + /** + * expected rename(NodeRef, String) - got something else... + */ + throw new AlfrescoRuntimeException("FilenameFilteringInterceptor: unknown rename method"); + } } else { diff --git a/source/java/org/alfresco/repo/model/filefolder/HiddenAspect.java b/source/java/org/alfresco/repo/model/filefolder/HiddenAspect.java index 54a6c88e3c..b2580012b8 100644 --- a/source/java/org/alfresco/repo/model/filefolder/HiddenAspect.java +++ b/source/java/org/alfresco/repo/model/filefolder/HiddenAspect.java @@ -559,16 +559,6 @@ public class HiddenAspect return filter.pattern(); } - public Set getVisibility() - { - return clientVisibility; - } - - public Set getHiddenAttribute() - { - return hiddenAttribute; - } - public int getVisibilityMask() { return visibilityMask; diff --git a/source/java/org/alfresco/repo/node/cleanup/TransactionCleanupTest.java b/source/java/org/alfresco/repo/node/cleanup/TransactionCleanupTest.java new file mode 100644 index 0000000000..e3da0d993c --- /dev/null +++ b/source/java/org/alfresco/repo/node/cleanup/TransactionCleanupTest.java @@ -0,0 +1,242 @@ +package org.alfresco.repo.node.cleanup; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +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.node.NodeDAO; +import org.alfresco.repo.domain.node.Transaction; +import org.alfresco.repo.node.db.DeletedNodeCleanupWorker; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.ServiceRegistry; +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.MutableAuthenticationService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.ApplicationContextHelper; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springframework.context.ApplicationContext; +import org.springframework.extensions.webscripts.GUID; + +public class TransactionCleanupTest +{ + private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); + + private TransactionService transactionService; + private NodeService nodeService; + private SearchService searchService; + private MutableAuthenticationService authenticationService; + private NodeDAO nodeDAO; + private DeletedNodeCleanupWorker worker; + + private NodeRef nodeRef1; + private NodeRef nodeRef2; + private NodeRef nodeRef3; + private NodeRef nodeRef4; + private NodeRef nodeRef5; + private RetryingTransactionHelper helper; + + @Before + public void before() + { + ServiceRegistry serviceRegistry = (ServiceRegistry)ctx.getBean("ServiceRegistry"); + this.transactionService = serviceRegistry.getTransactionService(); + this.authenticationService = (MutableAuthenticationService)ctx.getBean("authenticationService"); + this.nodeService = serviceRegistry.getNodeService(); + this.searchService = serviceRegistry.getSearchService(); + this.nodeDAO = (NodeDAO)ctx.getBean("nodeDAO"); + this.worker = (DeletedNodeCleanupWorker)ctx.getBean("nodeCleanup.deletedNodeCleanup"); + this.worker.setMinPurgeAgeDays(0); + + this.helper = transactionService.getRetryingTransactionHelper(); + authenticationService.authenticate("admin", "admin".toCharArray()); + + StoreRef storeRef = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore"); + ResultSet resultSet = searchService.query(storeRef, SearchService.LANGUAGE_LUCENE, "PATH:\"/app:company_home\""); + final NodeRef companyHome = resultSet.getNodeRef(0); + resultSet.close(); + + RetryingTransactionHelper.RetryingTransactionCallback createNode = new RetryingTransactionHelper.RetryingTransactionCallback() + { + @Override + public NodeRef execute() throws Throwable + { + return nodeService.createNode(companyHome, ContentModel.ASSOC_CONTAINS, QName.createQName("test", GUID.generate()), ContentModel.TYPE_CONTENT).getChildRef(); + } + }; + this.nodeRef1 = helper.doInTransaction(createNode, false, true); + this.nodeRef2 = helper.doInTransaction(createNode, false, true); + this.nodeRef3 = helper.doInTransaction(createNode, false, true); + this.nodeRef4 = helper.doInTransaction(createNode, false, true); + this.nodeRef5 = helper.doInTransaction(createNode, false, true); + } + + private Map> createTransactions() + { + Map> txnIds = new HashMap>(); + + UpdateNode updateNode1 = new UpdateNode(nodeRef1); + UpdateNode updateNode2 = new UpdateNode(nodeRef2); + UpdateNode updateNode3 = new UpdateNode(nodeRef3); + UpdateNode updateNode4 = new UpdateNode(nodeRef4); + UpdateNode updateNode5 = new UpdateNode(nodeRef5); + + List txnIds1 = new ArrayList(); + List txnIds2 = new ArrayList(); + List txnIds3 = new ArrayList(); + List txnIds4 = new ArrayList(); + List txnIds5 = new ArrayList(); + txnIds.put(nodeRef1, txnIds1); + txnIds.put(nodeRef2, txnIds2); + txnIds.put(nodeRef3, txnIds3); + txnIds.put(nodeRef4, txnIds4); + txnIds.put(nodeRef5, txnIds5); + + for(int i = 0; i < 10; i++) + { + String txnId1 = helper.doInTransaction(updateNode1, false, true); + txnIds1.add(txnId1); + if(i == 0) + { + String txnId2 = helper.doInTransaction(updateNode2, false, true); + txnIds2.add(txnId2); + } + if(i == 2) + { + String txnId3 = helper.doInTransaction(updateNode3, false, true); + txnIds3.add(txnId3); + } + if(i == 3) + { + String txnId4 = helper.doInTransaction(updateNode4, false, true); + txnIds4.add(txnId4); + } + if(i == 4) + { + String txnId5 = helper.doInTransaction(updateNode5, false, true); + txnIds5.add(txnId5); + } + } + + return txnIds; + } + + private boolean containsTransaction(List txns, String txnId) + { + boolean found = false; + for(Transaction tx : txns) + { + if(tx.getChangeTxnId().equals(txnId)) + { + found = true; + break; + } + } + return found; + } + + @Test + public void testPurgeUnusedTransactions() throws Exception + { + // Execute transactions that update a number of nodes. For nodeRef1, all but the last txn will be unused. + + // run the transaction cleaner to clean up any existing unused transactions + worker.doClean(); + + long start = System.currentTimeMillis(); + Long minTxnId = nodeDAO.getMinTxnId(); + + final Map> txnIds = createTransactions(); + final List txnIds1 = txnIds.get(nodeRef1); + final List txnIds2 = txnIds.get(nodeRef2); + final List txnIds3 = txnIds.get(nodeRef3); + final List txnIds4 = txnIds.get(nodeRef4); + final List txnIds5 = txnIds.get(nodeRef5); + + // run the transaction cleaner + worker.setPurgeSize(5); // small purge size + worker.doClean(); + + // Get transactions committed after the test started + List txns = nodeDAO.getTxnsByCommitTimeAscending(Long.valueOf(start), Long.valueOf(Long.MAX_VALUE), Integer.MAX_VALUE, null, false); + + List expectedUnusedTxnIds = new ArrayList(10); + expectedUnusedTxnIds.addAll(txnIds1.subList(0, txnIds1.size() - 1)); + + List expectedUsedTxnIds = new ArrayList(5); + expectedUsedTxnIds.add(txnIds1.get(txnIds1.size() - 1)); + expectedUsedTxnIds.addAll(txnIds2); + expectedUsedTxnIds.addAll(txnIds3); + expectedUsedTxnIds.addAll(txnIds4); + expectedUsedTxnIds.addAll(txnIds5); + + // check that the correct transactions have been purged i.e. all except the last one to update the node + // i.e. in this case, all but the last one in txnIds1 + int numFoundUnusedTxnIds = 0; + for(String txnId : expectedUnusedTxnIds) + { + if(!containsTransaction(txns, txnId)) + { + numFoundUnusedTxnIds++; + } + else if(txnIds1.contains(txnId)) + { + fail("Unused transaction(s) were not purged: " + txnId); + } + } + assertEquals(9, numFoundUnusedTxnIds); + + // check that the correct transactions remain i.e. all those in txnIds2, txnIds3, txnIds4 and txnIds5 + int numFoundUsedTxnIds = 0; + for(String txnId : expectedUsedTxnIds) + { + if(containsTransaction(txns, txnId)) + { + numFoundUsedTxnIds++; + } + } + + assertEquals(5, numFoundUsedTxnIds); + + List txnsUnused = nodeDAO.getTxnsUnused(minTxnId, Long.MAX_VALUE, Integer.MAX_VALUE); + assertEquals(0, txnsUnused.size()); + } + + @After + public void after() + { + ApplicationContextHelper.closeApplicationContext(); + } + + private class UpdateNode implements RetryingTransactionCallback + { + private NodeRef nodeRef; + + UpdateNode(NodeRef nodeRef) + { + this.nodeRef = nodeRef; + } + + @Override + public String execute() throws Throwable + { + nodeService.setProperty(nodeRef, ContentModel.PROP_NAME, GUID.generate()); + String txnId = AlfrescoTransactionSupport.getTransactionId(); + + return txnId; + } + }; +} diff --git a/source/java/org/alfresco/repo/node/db/DeletedNodeCleanupWorker.java b/source/java/org/alfresco/repo/node/db/DeletedNodeCleanupWorker.java index f06094ad53..86d95f79d7 100644 --- a/source/java/org/alfresco/repo/node/db/DeletedNodeCleanupWorker.java +++ b/source/java/org/alfresco/repo/node/db/DeletedNodeCleanupWorker.java @@ -22,10 +22,10 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import org.alfresco.repo.domain.node.NodeDAO; import org.alfresco.repo.node.cleanup.AbstractNodeCleanupWorker; import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; -import org.apache.commons.lang.mutable.MutableLong; /** * Cleans up deleted nodes and dangling transactions that are old enough. @@ -37,6 +37,10 @@ public class DeletedNodeCleanupWorker extends AbstractNodeCleanupWorker { private long minPurgeAgeMs; + // Unused transactions will be purged in chunks determined by commit time boundaries. 'index.tracking.purgeSize' specifies the size + // of the chunk (in ms). Default is a couple of hours. + private int purgeSize = 7200000; // ms + /** * Default constructor */ @@ -78,6 +82,16 @@ public class DeletedNodeCleanupWorker extends AbstractNodeCleanupWorker } /** + * Set the purge transaction block size. This determines how many unused transactions are purged in one go. + * + * @param txnBlockSize + */ + public void setPurgeSize(int purgeSize) + { + this.purgeSize = purgeSize; + } + + /** * Cleans up deleted nodes that are older than the given minimum age. * * @param minAge the minimum age of a transaction or deleted node @@ -139,7 +153,6 @@ public class DeletedNodeCleanupWorker extends AbstractNodeCleanupWorker return results; } - private static final int TXN_PURGE_BATCH_SIZE = 50; /** * Cleans up unused transactions that are older than the given minimum age. * @@ -153,50 +166,37 @@ public class DeletedNodeCleanupWorker extends AbstractNodeCleanupWorker return Collections.emptyList(); } final List results = new ArrayList(100); - final MutableLong minTxnId = new MutableLong(0L); final long maxCommitTime = System.currentTimeMillis() - minAge; - RetryingTransactionCallback purgeTxnsCallback = new RetryingTransactionCallback() - { - public Integer execute() throws Throwable - { - final List txnIds = nodeDAO.getTxnsUnused( - minTxnId.longValue(), - maxCommitTime, - TXN_PURGE_BATCH_SIZE); - for (Long txnId : txnIds) - { - nodeDAO.purgeTxn(txnId); - // Update the min node ID for the next query - if (txnId.longValue() > minTxnId.longValue()) - { - minTxnId.setValue(txnId.longValue()); - } - } - return txnIds.size(); - } - }; + long fromCommitTime = nodeDAO.getMinUnusedTxnCommitTime().longValue(); + + // delete unused transactions in batches of size 'purgeTxnBlockSize' while (true) { // Ensure we keep the lock refreshLock(); - + RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper(); txnHelper.setMaxRetries(5); // Limit number of retries txnHelper.setRetryWaitIncrementMs(1000); // 1 second to allow other cleanups time to get through - // Get nodes to delete - Integer purgeCount = new Integer(0); - // Purge nodes + + long toCommitTime = fromCommitTime + purgeSize; + if(toCommitTime >= maxCommitTime) + { + toCommitTime = maxCommitTime; + } + + // Purge transactions try { - purgeCount = txnHelper.doInTransaction(purgeTxnsCallback, false, true); - if (purgeCount.intValue() > 0) + DeleteTransactionsCallback purgeTxnsCallback = new DeleteTransactionsCallback(nodeDAO, fromCommitTime, toCommitTime); + long purgeCount = txnHelper.doInTransaction(purgeTxnsCallback, false, true); + if (purgeCount > 0) { String msg = "Purged old txns: \n" + - " Min txn ID: " + minTxnId.longValue() + "\n" + - " Batch size: " + TXN_PURGE_BATCH_SIZE + "\n" + - " Max commit time: " + maxCommitTime + "\n" + + " From commit time (ms): " + fromCommitTime + "\n" + + " To commit time (ms): " + toCommitTime + "\n" + " Purge count: " + purgeCount; results.add(msg); } @@ -206,9 +206,8 @@ public class DeletedNodeCleanupWorker extends AbstractNodeCleanupWorker String msg = "Failed to purge txns." + " Set log level to WARN for this class to get exception log: \n" + - " Min txn ID: " + minTxnId.longValue() + "\n" + - " Batch size: " + TXN_PURGE_BATCH_SIZE + "\n" + - " Max commit time: " + maxCommitTime + "\n" + + " From commit time: " + fromCommitTime + "\n" + + " To commit time (ms): " + toCommitTime + "\n" + " Error: " + e.getMessage(); // It failed; do a full log in WARN mode if (logger.isWarnEnabled()) @@ -222,12 +221,37 @@ public class DeletedNodeCleanupWorker extends AbstractNodeCleanupWorker results.add(msg); break; } - if (purgeCount.intValue() == 0) + + fromCommitTime += purgeSize; + if(fromCommitTime >= maxCommitTime) { - break; + break; } } // Done return results; } + + /* + * Delete a block of unused transactions + */ + private static class DeleteTransactionsCallback implements RetryingTransactionCallback + { + private NodeDAO nodeDAO; + private long fromCommitTime; + private long toCommitTime; + + DeleteTransactionsCallback(NodeDAO nodeDAO, long fromCommitTime, long toCommitTime) + { + this.nodeDAO = nodeDAO; + this.fromCommitTime = fromCommitTime; + this.toCommitTime = toCommitTime; + } + + public Long execute() throws Throwable + { + long count = nodeDAO.deleteTxnsUnused(fromCommitTime, toCommitTime); + return count; + } + } } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/node/index/AbstractReindexComponent.java b/source/java/org/alfresco/repo/node/index/AbstractReindexComponent.java index c276195615..bbcc3b1708 100644 --- a/source/java/org/alfresco/repo/node/index/AbstractReindexComponent.java +++ b/source/java/org/alfresco/repo/node/index/AbstractReindexComponent.java @@ -21,7 +21,7 @@ package org.alfresco.repo.node.index; import java.io.PrintWriter; import java.io.Serializable; import java.io.StringWriter; -import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -53,6 +53,7 @@ import org.alfresco.repo.transaction.TransactionServiceImpl; import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; 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; @@ -62,7 +63,6 @@ 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; -import org.alfresco.util.Pair; import org.alfresco.util.ParameterCheck; import org.alfresco.util.PropertyCheck; import org.alfresco.util.VmShutdownListener; @@ -70,6 +70,7 @@ 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.springframework.dao.ConcurrencyFailureException; /** * Abstract helper for reindexing. @@ -278,6 +279,31 @@ public abstract class AbstractReindexComponent implements IndexRecovery * */ protected abstract void reindexImpl(); + + /** + * To allow for possible 'read committed' behaviour in some databases, where a node that previously existed during a + * transaction can disappear from existence, we treat InvalidNodeRefExceptions as concurrency conditions. + */ + protected T2 doInRetryingTransaction(final RetryingTransactionCallback callback, boolean isReadThrough) + { + return transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public T2 execute() throws Throwable + { + try + { + return callback.execute(); + } + catch (InvalidNodeRefException e) + { + // Turn InvalidNodeRefExceptions into retryable exceptions. + throw new ConcurrencyFailureException("Possible cache integrity issue during reindexing", e); + } + + } + }, true, isReadThrough); + } /** * If this object is currently busy, then it just nothing @@ -316,7 +342,7 @@ public abstract class AbstractReindexComponent implements IndexRecovery }; if (requireTransaction()) { - transactionService.getRetryingTransactionHelper().doInTransaction(reindexWork, true); + doInRetryingTransaction(reindexWork, false); } else { @@ -611,14 +637,14 @@ public abstract class AbstractReindexComponent implements IndexRecovery public InIndex isTxnPresentInIndex(final Transaction txn, final boolean readThrough) { - return transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + return doInRetryingTransaction(new RetryingTransactionCallback() { @Override public InIndex execute() throws Throwable { return isTxnPresentInIndex(txn); } - }, true, readThrough); + }, readThrough); } /** @@ -758,7 +784,7 @@ public abstract class AbstractReindexComponent implements IndexRecovery * * @throws ReindexTerminatedException if the VM is shutdown during the reindex */ - protected void reindexTransaction(final long txnId, ReindexNodeCallback callback, boolean isFull) + protected void reindexTransaction(final long txnId, final ReindexNodeCallback callback, final boolean isFull) { ParameterCheck.mandatory("txnId", txnId); if (logger.isDebugEnabled()) @@ -772,91 +798,153 @@ public abstract class AbstractReindexComponent implements IndexRecovery // The indexer will 'read through' to the latest database changes for the rest of this transaction indexer.setReadThrough(true); + + // Compile the complete set of transaction changes - we need to 'read through' here too + final Collection deletedNodes = new LinkedList(); + final Collection updatedNodes = new LinkedList(); + final Collection createdNodes = new LinkedList(); + final Collection deletedParents = new LinkedList(); + final Collection addedParents = new LinkedList(); - // get the node references pertinent to the transaction - We need to 'read through' here too - List> nodePairs = transactionService.getRetryingTransactionHelper().doInTransaction( - new RetryingTransactionCallback>>() - { - - @Override - public List> execute() throws Throwable - { - List nodeStatuses = nodeDAO.getTxnChanges(txnId); - List> nodePairs = new ArrayList>(nodeStatuses.size()); - for (NodeRef.Status nodeStatus : nodeStatuses) - { - ChildAssociationRef parent = nodeStatus.isDeleted() ? null : nodeService.getPrimaryParent(nodeStatus.getNodeRef()); - nodePairs.add(new Pair(nodeStatus, parent)); - } - return nodePairs; - } - }, true, true); - - // reindex each node - int nodeCount = 0; - for (Pair nodePair: nodePairs) + doInRetryingTransaction(new RetryingTransactionCallback() { - NodeRef.Status nodeStatus = nodePair.getFirst(); - NodeRef nodeRef = nodeStatus.getNodeRef(); - - if (nodeStatus.isDeleted()) // node deleted + @Override + public Void execute() throws Throwable { - if(isFull == false) + // process the node references pertinent to the transaction + List nodeStatuses = nodeDAO.getTxnChanges(txnId); + int nodeCount = 0; + for (NodeRef.Status nodeStatus : nodeStatuses) { - // only the child node ref is relevant - ChildAssociationRef assocRef = new ChildAssociationRef( - ContentModel.ASSOC_CHILDREN, - null, - null, - nodeRef); - indexer.deleteNode(assocRef); - if (logger.isDebugEnabled()) + NodeRef nodeRef = nodeStatus.getNodeRef(); + if (isFull) { - logger.debug("DELETE: " + nodeRef); - } - } - } - else // node created - { - if(isFull) - { - ChildAssociationRef assocRef = new ChildAssociationRef( - ContentModel.ASSOC_CHILDREN, - null, - null, - nodeRef); - indexer.createNode(assocRef); - if (logger.isDebugEnabled()) - { - logger.debug("CREATE: " + nodeRef); - } - } - else - { - // reindex - force a cascade reindex if possible (to account for a possible move) - ChildAssociationRef parent = nodePair.getSecond(); - if (parent == null) - { - indexer.updateNode(nodeRef); - if (logger.isDebugEnabled()) + if (!nodeStatus.isDeleted()) { - logger.debug("UPDATE: " + nodeRef); + createdNodes.add(new ChildAssociationRef(ContentModel.ASSOC_CHILDREN, null, null, nodeRef)); } } + else if (nodeStatus.isDeleted()) // node deleted + { + // only the child node ref is relevant + deletedNodes.add(new ChildAssociationRef(ContentModel.ASSOC_CHILDREN, null, null, nodeRef)); + } else { - indexer.createChildRelationship(parent); - if (logger.isDebugEnabled()) - { - logger.debug("MOVE: " + nodeRef + ", " + parent); - } + // Do a DB / index comparision to determine indexing operations required + indexer.detectNodeChanges(nodeRef, searcher, addedParents, deletedParents, createdNodes, + updatedNodes); + } + + // Check for VM shutdown every 100 nodes + if (++nodeCount % 100 == 0 && isShuttingDown()) + { + // We can't fail gracefully and run the risk of committing a half-baked transaction + logger.info("Reindexing of transaction " + txnId + " terminated by VM shutdown."); + throw new ReindexTerminatedException(); } } + return null; } + }, true); + + int nodeCount = 0; + for (ChildAssociationRef deletedNode: deletedNodes) + { + if (logger.isDebugEnabled()) + { + logger.debug("DELETE: " + deletedNode.getChildRef()); + } + indexer.deleteNode(deletedNode); + // Make the callback if (callback != null) { - callback.reindexedNode(nodeRef); + callback.reindexedNode(deletedNode.getChildRef()); + } + // Check for VM shutdown every 100 nodes + if (++nodeCount % 100 == 0 && isShuttingDown()) + { + // We can't fail gracefully and run the risk of committing a half-baked transaction + logger.info("Reindexing of transaction " + txnId + " terminated by VM shutdown."); + throw new ReindexTerminatedException(); + } + } + for (NodeRef updatedNode: updatedNodes) + { + if (logger.isDebugEnabled()) + { + logger.debug("UPDATE: " + updatedNode); + } + indexer.updateNode(updatedNode); + + // Make the callback + if (callback != null) + { + callback.reindexedNode(updatedNode); + } + // Check for VM shutdown every 100 nodes + if (++nodeCount % 100 == 0 && isShuttingDown()) + { + // We can't fail gracefully and run the risk of committing a half-baked transaction + logger.info("Reindexing of transaction " + txnId + " terminated by VM shutdown."); + throw new ReindexTerminatedException(); + } + } + for (ChildAssociationRef createdNode: createdNodes) + { + if (logger.isDebugEnabled()) + { + logger.debug("CREATE: " + createdNode.getChildRef()); + } + indexer.createNode(createdNode); + + // Make the callback + if (callback != null) + { + callback.reindexedNode(createdNode.getChildRef()); + } + // Check for VM shutdown every 100 nodes + if (++nodeCount % 100 == 0 && isShuttingDown()) + { + // We can't fail gracefully and run the risk of committing a half-baked transaction + logger.info("Reindexing of transaction " + txnId + " terminated by VM shutdown."); + throw new ReindexTerminatedException(); + } + } + for (ChildAssociationRef deletedParent: deletedParents) + { + if (logger.isDebugEnabled()) + { + logger.debug("UNLINK: " + deletedParent); + } + indexer.deleteChildRelationship(deletedParent); + + // Make the callback + if (callback != null) + { + callback.reindexedNode(deletedParent.getChildRef()); + } + // Check for VM shutdown every 100 nodes + if (++nodeCount % 100 == 0 && isShuttingDown()) + { + // We can't fail gracefully and run the risk of committing a half-baked transaction + logger.info("Reindexing of transaction " + txnId + " terminated by VM shutdown."); + throw new ReindexTerminatedException(); + } + } + for (ChildAssociationRef addedParent: addedParents) + { + if (logger.isDebugEnabled()) + { + logger.debug("LINK: " + addedParents); + } + indexer.createChildRelationship(addedParent); + + // Make the callback + if (callback != null) + { + callback.reindexedNode(addedParent.getChildRef()); } // Check for VM shutdown every 100 nodes if (++nodeCount % 100 == 0 && isShuttingDown()) @@ -999,7 +1087,7 @@ public abstract class AbstractReindexComponent implements IndexRecovery loggerOnThread.debug(msg); } // Do the work - transactionService.getRetryingTransactionHelper().doInTransaction(reindexCallback, true, true); + doInRetryingTransaction(reindexCallback, true); } catch (ReindexTerminatedException e) { @@ -1200,7 +1288,7 @@ public abstract class AbstractReindexComponent implements IndexRecovery return null; } }; - transactionService.getRetryingTransactionHelper().doInTransaction(reindexCallback, true, true); + doInRetryingTransaction(reindexCallback, true); return; } diff --git a/source/java/org/alfresco/repo/node/index/FullIndexRecoveryComponent.java b/source/java/org/alfresco/repo/node/index/FullIndexRecoveryComponent.java index cfe6d49d38..922f197253 100644 --- a/source/java/org/alfresco/repo/node/index/FullIndexRecoveryComponent.java +++ b/source/java/org/alfresco/repo/node/index/FullIndexRecoveryComponent.java @@ -19,6 +19,7 @@ package org.alfresco.repo.node.index; import java.util.ArrayList; +import java.util.Collections; import java.util.Date; import java.util.Iterator; import java.util.List; @@ -185,17 +186,12 @@ public class FullIndexRecoveryComponent extends AbstractReindexComponent transactionService.setAllowWrite(false, vetoName); } - - List startTxns = nodeDAO.getTxnsByCommitTimeAscending( - Long.MIN_VALUE, Long.MAX_VALUE, 1000, null, false); + List startTxns = getTxnsByCommitTimeWindowAscending(MIN_SAMPLE_TIME, 1000); InIndex startAllPresent = areTxnsInStartSample(startTxns); - - List endTxns = nodeDAO.getTxnsByCommitTimeDescending( - Long.MIN_VALUE, Long.MAX_VALUE, 1000, null, false); + List endTxns = getTxnsByCommitTimeWindowDescending(MIN_SAMPLE_TIME, 1000); InIndex endAllPresent = areAllTxnsInEndSample(endTxns); - - + // check the level of cover required switch (recoveryMode) { @@ -231,6 +227,135 @@ public class FullIndexRecoveryComponent extends AbstractReindexComponent } + /** + * List transactions up to the specified amount using a sliding time based window. + * This will create smaller result sets which circumvents performance problems using + * sql LIMIT on some jdbc drivers and databases. + * + * @param windowSize the size of collection window in milliseconds. + * @param count the number of transctions to attempt to collect + * @return returns a list of transactions + */ + private List getTxnsByCommitTimeWindowDescending(final long minWindowSize, final int count) { + if (minWindowSize == 0 || count == 0) + { + return Collections.emptyList(); + } + final long startTxns = nodeDAO.getMinTxnCommitTime(); + final long endTxns = nodeDAO.getMaxTxnCommitTime() + 1; + + List list = new ArrayList(count); + long toTimeExclusive = endTxns; + long window = minWindowSize; + //start at the end move backward by sample increment + while (true) + { + //slide window start backward by sample increment (windowsSize is a negative number) + long fromTimeInclusive = toTimeExclusive - window; + if (fromTimeInclusive <= startTxns) + { + //if we have moved back past the first transaction set to zero + fromTimeInclusive = startTxns; + } + List txns = nodeDAO.getTxnsByCommitTimeDescending( + fromTimeInclusive, toTimeExclusive, count, null, false); + + for (Transaction txn : txns) + { + list.add(txn); + //bail out if we have enough + if (list.size() >= count) break; + } + //bail out of main loop if we have enough or there are no more transactions + if (list.size() >= count || fromTimeInclusive == startTxns) + { + break; + } + //calculate new window size + if (list.size() == 0) + { + window = minWindowSize; + } + else + { + //calculate rate of transactions found (start to end of current window) + window = Math.max(minWindowSize, count + * (endTxns - fromTimeInclusive) / list.size()); + } + + //slide window end back to last inclusive time window + toTimeExclusive = fromTimeInclusive; + } + return list; + } + + /** + * List transactions up to the specified amount using a sliding time based window. + * This will create smaller result sets which circumvents performance problems using + * sql LIMIT on some jdbc drivers and databases. + * + * @param windowSize the size of collection window in milliseconds. + * @param count the number of transctions to attempt to collect + * @return returns a list of transactions + */ + private List getTxnsByCommitTimeWindowAscending(final long minWindowSize, final int count) { + if (minWindowSize == 0 || count == 0) + { + return Collections.emptyList(); + } + + //TODO: these return null for no transactions + final long startTxns = nodeDAO.getMinTxnCommitTime(); + final long endTxns = nodeDAO.getMaxTxnCommitTime() + 1; + + long window = minWindowSize; + List list = new ArrayList(count); + if ( window > 0) + { + long fromTimeInclusive = startTxns; + //start at the beginning move forward by sample increment + while (true) + { + //slide window end out from window start to window start + increment + long toTimeExclusive = fromTimeInclusive + window; + //if window size pushes window beyond end transaction then clip it to end + if (toTimeExclusive >= endTxns) { + toTimeExclusive = endTxns; + } + List txns = nodeDAO.getTxnsByCommitTimeAscending( + fromTimeInclusive, toTimeExclusive, count, null, false); + + for (Transaction txn : txns) + { + list.add(txn); + //bail out if we have enough + if (list.size() >= count) break; + } + //bail out of main loop if we have enough or there are no more transactions + if (list.size() >= count || toTimeExclusive >= endTxns) + { + break; + } + //calculate new window size + if (list.size() == 0) + { + window = minWindowSize; + } + else + { + //calculate rate of transactions found (start to end of current window) + window = Math.max(minWindowSize, count + * (toTimeExclusive - startTxns) / list.size()); + } + + //slide window start forward to last exclusive time window + fromTimeInclusive = toTimeExclusive; + } + } + return list; + } + + /** * @return Returns false if any one of the transactions aren't in the index. */ diff --git a/source/java/org/alfresco/repo/node/index/IndexTransactionTrackerTest.java b/source/java/org/alfresco/repo/node/index/IndexTransactionTrackerTest.java index eaaee714b6..150f1aff1a 100644 --- a/source/java/org/alfresco/repo/node/index/IndexTransactionTrackerTest.java +++ b/source/java/org/alfresco/repo/node/index/IndexTransactionTrackerTest.java @@ -29,6 +29,7 @@ import org.alfresco.repo.management.subsystems.ChildApplicationContextFactory; import org.alfresco.repo.search.Indexer; import org.alfresco.repo.search.impl.lucene.fts.FullTextSearchIndexer; import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.tenant.TenantService; import org.alfresco.repo.transaction.TransactionServiceImpl; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.ServiceRegistry; @@ -90,6 +91,7 @@ public class IndexTransactionTrackerTest extends TestCase indexTracker.setThreadPoolExecutor(threadPoolExecutor); indexTracker.setSearcher(searchService); indexTracker.setTransactionService((TransactionServiceImpl)transactionService); + indexTracker.setTenantService((TenantService) ctx.getBean("tenantService")); // authenticate authenticationComponent.setSystemUserAsCurrentUser(); diff --git a/source/java/org/alfresco/repo/search/Indexer.java b/source/java/org/alfresco/repo/search/Indexer.java index c21111b934..6068a2983d 100644 --- a/source/java/org/alfresco/repo/search/Indexer.java +++ b/source/java/org/alfresco/repo/search/Indexer.java @@ -18,9 +18,13 @@ */ package org.alfresco.repo.search; +import java.util.Collection; + +import org.alfresco.repo.search.impl.lucene.LuceneIndexException; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.search.SearchService; /** * This interface abstracts how indexing is used from within the node service @@ -103,6 +107,27 @@ public interface Indexer */ public void deleteChildRelationship(ChildAssociationRef relationshipRef); + /** + * Does a database vs index comparison for the given created/updated/renamed/referenced nodeRef in order to + * determine the set of indexing operations required + * + * @param nodeRef + * the nodeRef to process + * @param searcher + * searcher to query the indexes + * @param addedParents + * set to add new secondary parent associations to + * @param deletedParents + * set to add removed secondary parent associations to + * @param createdNodes + * set to add created nodes to + * @param updatedNodes + * set to add updated nodes to + */ + public void detectNodeChanges(NodeRef nodeRef, SearchService searcher, + Collection addedParents, Collection deletedParents, + Collection createdNodes, Collection updatedNodes); + /** * Delete the index for a store * @param storeRef diff --git a/source/java/org/alfresco/repo/search/IndexerComponent.java b/source/java/org/alfresco/repo/search/IndexerComponent.java index 50de7a0059..5b0ec5b1b9 100644 --- a/source/java/org/alfresco/repo/search/IndexerComponent.java +++ b/source/java/org/alfresco/repo/search/IndexerComponent.java @@ -18,11 +18,14 @@ */ package org.alfresco.repo.search; +import java.util.Collection; + import org.alfresco.repo.service.StoreRedirectorProxyFactory; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.search.SearchService; import org.springframework.extensions.surf.util.AbstractLifecycleBean; import org.springframework.context.ApplicationEvent; @@ -118,6 +121,14 @@ public class IndexerComponent extends AbstractLifecycleBean implements Indexer Indexer indexer = getIndexer(relationshipRef.getChildRef().getStoreRef()); indexer.deleteChildRelationship(relationshipRef); } + + public void detectNodeChanges(NodeRef nodeRef, SearchService searcher, + Collection addedParents, Collection deletedParents, + Collection createdNodes, Collection updatedNodes) + { + Indexer indexer = getIndexer(nodeRef.getStoreRef()); + indexer.detectNodeChanges(nodeRef, searcher, addedParents, deletedParents, createdNodes, updatedNodes); + } /* (non-Javadoc) * @see org.alfresco.repo.search.Indexer#deleteIndex(org.alfresco.service.cmr.repository.StoreRef) diff --git a/source/java/org/alfresco/repo/search/NodeServiceXPath.java b/source/java/org/alfresco/repo/search/NodeServiceXPath.java index faaf9b4155..312cb14a99 100644 --- a/source/java/org/alfresco/repo/search/NodeServiceXPath.java +++ b/source/java/org/alfresco/repo/search/NodeServiceXPath.java @@ -75,7 +75,6 @@ import org.jaxen.function.TrueFunction; import org.jaxen.function.ext.EndsWithFunction; import org.jaxen.function.ext.EvaluateFunction; import org.jaxen.function.ext.LowerFunction; -import org.jaxen.function.ext.MatrixConcatFunction; import org.jaxen.function.ext.UpperFunction; import org.jaxen.function.xslt.DocumentFunction; @@ -602,6 +601,9 @@ public class NodeServiceXPath extends BaseXPath registerFunction("", // namespace URI "document", new DocumentFunction()); + + registerFunction("", // namespace URI + "ends-with", new EndsWithFunction()); registerFunction("", // namespace URI "false", new FalseFunction()); @@ -673,9 +675,6 @@ public class NodeServiceXPath extends BaseXPath // extension functions should go into a namespace, but which one? // for now, keep them in default namespace to not break any code - registerFunction("", // namespace URI - "matrix-concat", new MatrixConcatFunction()); - registerFunction("", // namespace URI "evaluate", new EvaluateFunction()); diff --git a/source/java/org/alfresco/repo/search/impl/NoActionIndexer.java b/source/java/org/alfresco/repo/search/impl/NoActionIndexer.java index bb7f4dbdf1..3b98dbe428 100644 --- a/source/java/org/alfresco/repo/search/impl/NoActionIndexer.java +++ b/source/java/org/alfresco/repo/search/impl/NoActionIndexer.java @@ -18,10 +18,13 @@ */ package org.alfresco.repo.search.impl; +import java.util.Collection; + import org.alfresco.repo.search.Indexer; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.search.SearchService; /** * A no action indexer - the indexing is done automatically along with @@ -68,6 +71,13 @@ public class NoActionIndexer implements Indexer { return; } + + public void detectNodeChanges(NodeRef nodeRef, SearchService searcher, + Collection addedParents, Collection deletedParents, + Collection createdNodes, Collection updatedNodes) + { + return; + } /* (non-Javadoc) * @see org.alfresco.repo.search.Indexer#deleteIndex(org.alfresco.service.cmr.repository.StoreRef) diff --git a/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneIndexerImpl.java b/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneIndexerImpl.java index ebb01f8298..2054c48b30 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneIndexerImpl.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneIndexerImpl.java @@ -79,6 +79,10 @@ import org.alfresco.service.cmr.repository.TransformationOptions; import org.alfresco.service.cmr.repository.Path.ChildAssocElement; import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; import org.alfresco.service.cmr.repository.datatype.TypeConversionException; +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; import org.alfresco.service.namespace.QName; import org.alfresco.util.CachingDateFormat; import org.alfresco.util.EqualsHelper; @@ -345,10 +349,10 @@ public class ADMLuceneIndexerImpl extends AbstractLuceneIndexerImpl imp throw new LuceneIndexException(event + " failed - node is not in the required store"); } final NodeRef parentRef = relationshipRef.getParentRef(); - final NodeRef childRef = relationshipRef.getChildRef(); - if (parentRef != null) + final NodeRef childRef = relationshipRef.getChildRef(); + if (parentRef != null && !relationshipRef.isPrimary()) { - // If the child has a lot of secondary parents, its cheaper to cascade reindex its parent + // If a SECONDARY child has a lot of secondary parents, its cheaper to cascade reindex the secondary parent // rather than itself if (doInReadthroughTransaction(new RetryingTransactionCallback() { @@ -648,6 +652,80 @@ public class ADMLuceneIndexerImpl extends AbstractLuceneIndexerImpl imp } } + public void detectNodeChanges(NodeRef nodeRef, SearchService searcher, + Collection addedParents, Collection deletedParents, + Collection createdNodes, Collection updatedNodes) throws LuceneIndexException + { + boolean nodeExisted = false; + boolean relationshipsChanged = false; + + ResultSet results = null; + SearchParameters sp = new SearchParameters(); + sp.setLanguage(SearchService.LANGUAGE_LUCENE); + sp.addStore(nodeRef.getStoreRef()); + try + { + sp.setQuery("ID:" + LuceneQueryParser.escape(nodeRef.toString())); + results = searcher.query(sp); + for (ResultSetRow row : results) + { + nodeExisted = true; + Document document = ((LuceneResultSetRow) row).getDocument(); + Field qname = document.getField("QNAME"); + if (qname == null) + { + continue; + } + Collection> allParents = getAllParents(nodeRef, nodeService.getProperties(nodeRef)); + Set dbParents = new HashSet(allParents.size() * 2); + for (Pair pair : allParents) + { + ChildAssociationRef qNameRef = tenantService.getName(pair.getFirst()); + if ((qNameRef != null) && (qNameRef.getParentRef() != null) && (qNameRef.getQName() != null)) + { + dbParents.add(new ChildAssociationRef(ContentModel.ASSOC_CHILDREN, qNameRef.getParentRef(), qNameRef.getQName(), qNameRef.getChildRef())); + } + } + + Field[] parents = document.getFields("PARENT"); + String[] qnames = qname.stringValue().split(";/"); + Set addedParentsSet = new HashSet(dbParents); + for (int i=0; i createDocumentsImpl(final String stringNodeRef, FTSStatus ftsStatus, boolean indexAllProperties, boolean includeDirectoryDocuments, final boolean cascade, final Set pathsProcessedSinceFlush, diff --git a/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneSearcherImpl.java b/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneSearcherImpl.java index 5eb003e7c0..40b4d0899d 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneSearcherImpl.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneSearcherImpl.java @@ -90,9 +90,8 @@ import org.apache.lucene.search.Query; import org.apache.lucene.search.Searcher; import org.apache.lucene.search.Sort; import org.apache.lucene.search.SortField; -import org.saxpath.SAXPathException; - -import com.werken.saxpath.XPathReader; +import org.jaxen.saxpath.SAXPathException; +import org.jaxen.saxpath.base.XPathReader; /** * The Lucene implementation of Searcher At the moment we support only lucene based queries. TODO: Support for other diff --git a/source/java/org/alfresco/repo/search/impl/lucene/AVMLuceneIndexerImpl.java b/source/java/org/alfresco/repo/search/impl/lucene/AVMLuceneIndexerImpl.java index 0cfdbfcb3c..c51e5352fe 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/AVMLuceneIndexerImpl.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/AVMLuceneIndexerImpl.java @@ -28,6 +28,7 @@ import java.io.UnsupportedEncodingException; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.HashSet; @@ -82,6 +83,7 @@ import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.repository.TransformationOptions; import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; import org.alfresco.service.cmr.repository.datatype.TypeConversionException; +import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.namespace.QName; import org.alfresco.util.CachingDateFormat; import org.alfresco.util.EqualsHelper; @@ -1591,6 +1593,14 @@ public class AVMLuceneIndexerImpl extends AbstractLuceneIndexerImpl impl throw new LuceneIndexException("Create node failed", e); } } + + @Override + public void detectNodeChanges(NodeRef nodeRef, SearchService searcher, + Collection addedParents, Collection deletedParents, + Collection createdNodes, Collection updatedNodes) + { + updatedNodes.add(nodeRef); + } public void updateNode(NodeRef nodeRef) { diff --git a/source/java/org/alfresco/repo/search/impl/lucene/DebugXPathHandler.java b/source/java/org/alfresco/repo/search/impl/lucene/DebugXPathHandler.java index 78f4355b27..0a6092a291 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/DebugXPathHandler.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/DebugXPathHandler.java @@ -18,11 +18,11 @@ */ package org.alfresco.repo.search.impl.lucene; -import org.saxpath.Axis; -import org.saxpath.SAXPathException; -import org.saxpath.XPathHandler; +import org.jaxen.saxpath.Axis; +import org.jaxen.saxpath.SAXPathException; +import org.jaxen.saxpath.XPathHandler; +import org.jaxen.saxpath.base.XPathReader; -import com.werken.saxpath.XPathReader; public class DebugXPathHandler implements XPathHandler { diff --git a/source/java/org/alfresco/repo/search/impl/lucene/LuceneAlfrescoXPathQueryLanguage.java b/source/java/org/alfresco/repo/search/impl/lucene/LuceneAlfrescoXPathQueryLanguage.java index 37ab6e92b2..4749d9da9c 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/LuceneAlfrescoXPathQueryLanguage.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/LuceneAlfrescoXPathQueryLanguage.java @@ -19,42 +19,17 @@ package org.alfresco.repo.search.impl.lucene; import java.io.IOException; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; import org.alfresco.repo.search.EmptyResultSet; import org.alfresco.repo.search.SearcherException; -import org.alfresco.repo.search.impl.parsers.AlfrescoFunctionEvaluationContext; -import org.alfresco.repo.search.impl.parsers.FTSParser; -import org.alfresco.repo.search.impl.parsers.FTSQueryParser; -import org.alfresco.repo.search.impl.querymodel.Argument; -import org.alfresco.repo.search.impl.querymodel.Column; -import org.alfresco.repo.search.impl.querymodel.Constraint; -import org.alfresco.repo.search.impl.querymodel.Function; -import org.alfresco.repo.search.impl.querymodel.Order; -import org.alfresco.repo.search.impl.querymodel.Ordering; -import org.alfresco.repo.search.impl.querymodel.QueryEngine; -import org.alfresco.repo.search.impl.querymodel.QueryEngineResults; -import org.alfresco.repo.search.impl.querymodel.QueryModelFactory; -import org.alfresco.repo.search.impl.querymodel.QueryOptions; -import org.alfresco.repo.search.impl.querymodel.QueryOptions.Connective; -import org.alfresco.repo.search.impl.querymodel.impl.functions.PropertyAccessor; -import org.alfresco.repo.search.impl.querymodel.impl.functions.Score; -import org.alfresco.repo.search.impl.querymodel.impl.lucene.LuceneOrdering; -import org.alfresco.service.cmr.search.LimitBy; import org.alfresco.service.cmr.search.ResultSet; import org.alfresco.service.cmr.search.SearchParameters; import org.alfresco.service.cmr.search.SearchService; -import org.alfresco.service.cmr.search.SearchParameters.SortDefinition; -import org.alfresco.service.cmr.search.SearchParameters.SortDefinition.SortType; import org.apache.lucene.search.Hits; import org.apache.lucene.search.Query; import org.apache.lucene.search.Searcher; -import org.saxpath.SAXPathException; - -import com.werken.saxpath.XPathReader; +import org.jaxen.saxpath.SAXPathException; +import org.jaxen.saxpath.base.XPathReader; /** * Alfresco FTS Query language support 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 4b35b0aaac..74ad5bd4ef 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 @@ -94,15 +94,15 @@ import org.apache.lucene.store.FSDirectory; import org.apache.lucene.store.IndexInput; import org.apache.lucene.store.IndexOutput; import org.apache.lucene.store.RAMDirectory; +import org.jaxen.saxpath.SAXPathException; +import org.jaxen.saxpath.base.XPathReader; import org.safehaus.uuid.UUID; -import org.saxpath.SAXPathException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.event.ContextRefreshedEvent; -import com.werken.saxpath.XPathReader; /** * The information that makes up an index. IndexInfoVersion Repeated information of the form diff --git a/source/java/org/alfresco/repo/search/impl/noindex/NoIndexIndexer.java b/source/java/org/alfresco/repo/search/impl/noindex/NoIndexIndexer.java index 078f8305b7..61ea5683eb 100644 --- a/source/java/org/alfresco/repo/search/impl/noindex/NoIndexIndexer.java +++ b/source/java/org/alfresco/repo/search/impl/noindex/NoIndexIndexer.java @@ -18,11 +18,14 @@ */ package org.alfresco.repo.search.impl.noindex; +import java.util.Collection; + import org.alfresco.error.StackTraceUtil; import org.alfresco.repo.search.Indexer; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.search.SearchService; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -127,6 +130,21 @@ public class NoIndexIndexer implements Indexer trace(); return; } + + /* (non-Javadoc) + * @see org.alfresco.repo.search.Indexer#detectNodeChanges(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.search.SearchService, java.util.Collection, java.util.Collection, java.util.Collection, java.util.Collection) + */ + @Override + public void detectNodeChanges(NodeRef nodeRef, SearchService searcher, + Collection addedParents, Collection deletedParents, + Collection createdNodes, Collection updatedNodes) + { + if(s_logger.isDebugEnabled()) + { + s_logger.debug("detectNodeChanges = "+nodeRef); + } + trace(); + } /* (non-Javadoc) * @see org.alfresco.repo.search.Indexer#deleteIndex(org.alfresco.service.cmr.repository.StoreRef) 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 d9528910d4..3f07d77fae 100644 --- a/source/java/org/alfresco/repo/search/impl/solr/SolrQueryHTTPClient.java +++ b/source/java/org/alfresco/repo/search/impl/solr/SolrQueryHTTPClient.java @@ -185,7 +185,7 @@ public class SolrQueryHTTPClient url.append(encoder.encode(searchParameters.getQuery(), "UTF-8")); url.append("&wt=").append(encoder.encode("json", "UTF-8")); - url.append("&fl=").append(encoder.encode("*,score", "UTF-8")); + url.append("&fl=").append(encoder.encode("DBID,score", "UTF-8")); if (searchParameters.getMaxItems() >= 0) { diff --git a/source/java/org/alfresco/repo/security/authentication/TicketCleanupJob.java b/source/java/org/alfresco/repo/security/authentication/TicketCleanupJob.java new file mode 100644 index 0000000000..0f37a3f2e5 --- /dev/null +++ b/source/java/org/alfresco/repo/security/authentication/TicketCleanupJob.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.security.authentication; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.quartz.Job; +import org.quartz.JobDataMap; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; + +/** + * @author Andy + * + */ +public class TicketCleanupJob implements Job +{ + + public TicketCleanupJob() + { + } + + /** + * Calls the cleaner to do its work + */ + public void execute(JobExecutionContext context) throws JobExecutionException + { + JobDataMap jobData = context.getJobDetail().getJobDataMap(); + // extract the content cleaner to use + Object abstractAuthenticationServiceRef = jobData.get("abstractAuthenticationService"); + if (abstractAuthenticationServiceRef == null || !(abstractAuthenticationServiceRef instanceof AbstractAuthenticationService)) + { + throw new AlfrescoRuntimeException( + "ContentStoreCleanupJob data must contain valid 'contentStoreCleaner' reference"); + } + AbstractAuthenticationService abstractAuthenticationService = (AbstractAuthenticationService) abstractAuthenticationServiceRef; + abstractAuthenticationService.invalidateTickets(true); + } + +} diff --git a/source/java/org/alfresco/repo/security/permissions/impl/AbstractPermissionTest.java b/source/java/org/alfresco/repo/security/permissions/impl/AbstractPermissionTest.java index 1d96068cda..f24cb82ded 100644 --- a/source/java/org/alfresco/repo/security/permissions/impl/AbstractPermissionTest.java +++ b/source/java/org/alfresco/repo/security/permissions/impl/AbstractPermissionTest.java @@ -101,6 +101,8 @@ public class AbstractPermissionTest extends TestCase private UserTransaction testTX; + protected PermissionServiceImpl permissionServiceImpl; + public AbstractPermissionTest() { super(); @@ -120,6 +122,7 @@ public class AbstractPermissionTest extends TestCase dictionaryService = (DictionaryService) applicationContext.getBean(ServiceRegistry.DICTIONARY_SERVICE .getLocalName()); permissionService = (PermissionServiceSPI) applicationContext.getBean("permissionService"); + permissionServiceImpl = (PermissionServiceImpl) applicationContext.getBean("permissionServiceImpl"); namespacePrefixResolver = (NamespacePrefixResolver) applicationContext .getBean(ServiceRegistry.NAMESPACE_SERVICE.getLocalName()); authenticationService = (MutableAuthenticationService) applicationContext.getBean("authenticationService"); @@ -178,6 +181,8 @@ public class AbstractPermissionTest extends TestCase authenticationService.createAuthentication(AuthenticationUtil.getAdminUserName(), "admin".toCharArray()); authenticationComponent.clearCurrentSecurityContext(); + + assertTrue(permissionServiceImpl.getAnyDenyDenies()); } @Override diff --git a/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceImpl.java b/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceImpl.java index 24c217805d..f6f5dfd6c6 100644 --- a/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceImpl.java +++ b/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceImpl.java @@ -93,6 +93,8 @@ public class PermissionServiceImpl extends AbstractLifecycleBean implements Perm private SimpleCache accessCache; private SimpleCache> readersCache; + + private SimpleCache> readersDeniedCache; /* * Access to the model @@ -140,6 +142,8 @@ public class PermissionServiceImpl extends AbstractLifecycleBean implements Perm private PermissionReference allPermissionReference; + private boolean anyDenyDenies = false; + /** * Standard spring construction. */ @@ -152,6 +156,8 @@ public class PermissionServiceImpl extends AbstractLifecycleBean implements Perm // Inversion of control // + + /** * Set the dictionary service * @param dictionaryService @@ -161,6 +167,22 @@ public class PermissionServiceImpl extends AbstractLifecycleBean implements Perm this.dictionaryService = dictionaryService; } + /** + * @param anyDenyDenies the anyDenyDenies to set + */ + public void setAnyDenyDenies(boolean anyDenyDenies) + { + this.anyDenyDenies = anyDenyDenies; + accessCache.clear(); + readersCache.clear(); + readersDeniedCache.clear(); + } + + public boolean getAnyDenyDenies() + { + return anyDenyDenies; + } + /** * Set the permissions model dao * @@ -259,6 +281,15 @@ public class PermissionServiceImpl extends AbstractLifecycleBean implements Perm this.readersCache = readersCache; } + + /** + * @param readersDeniedCache the readersDeniedCache to set + */ + public void setReadersDeniedCache(SimpleCache> readersDeniedCache) + { + this.readersDeniedCache = readersDeniedCache; + } + /** * Set the policy component * @@ -1117,6 +1148,10 @@ public class PermissionServiceImpl extends AbstractLifecycleBean implements Perm private AccessStatus ownerRead(String username, NodeRef nodeRef) { + // Reviewed the behaviour of deny and ownership with Mike F + // ATM ownership takes precendence over READ deny + // TODO: check that global owner rights are set + AccessStatus result = AccessStatus.DENIED; String owner = ownableService.getOwner(nodeRef); @@ -1184,14 +1219,70 @@ public class PermissionServiceImpl extends AbstractLifecycleBean implements Perm return Collections.unmodifiableSet(readers); } + + /** + * @param aclId + * @return set of authorities with read permission on the ACL + */ + private Set buildReadersDenied(Long aclId) + { + HashSet assigned = new HashSet(); + HashSet denied = new HashSet(); + AccessControlList acl = aclDaoComponent.getAccessControlList(aclId); + + if (acl == null) + { + return denied; + } + + for (AccessControlEntry ace : acl.getEntries()) + { + assigned.add(ace.getAuthority()); + } + + for(String authority : assigned) + { + UnconditionalDeniedAclTest test = new UnconditionalDeniedAclTest(getPermissionReference(PermissionService.READ)); + if(test.evaluate(authority, aclId)) + { + denied.add(authority); + } + } + + return denied; + } private AccessStatus canRead(Long aclId) { Set authorities = getAuthorisations(); + // test denied + + if(anyDenyDenies) + { + + Set aclReadersDenied = readersDeniedCache.get(aclId); + if(aclReadersDenied == null) + { + aclReadersDenied = buildReadersDenied(aclId); + readersDeniedCache.put(aclId, aclReadersDenied); + } + + for(String auth : aclReadersDenied) + { + if(authorities.contains(auth)) + { + return AccessStatus.DENIED; + } + } + + } + + + // test acl readers Set aclReaders = getReaders(aclId); - + for(String auth : aclReaders) { if(authorities.contains(auth)) @@ -1210,6 +1301,8 @@ public class PermissionServiceImpl extends AbstractLifecycleBean implements Perm /** * Support class to test the permission on a node. * + * Not fixed up for deny as should not be used + * * @author Andy Hind */ private class NodeTest @@ -1658,6 +1751,29 @@ public class PermissionServiceImpl extends AbstractLifecycleBean implements Perm // any deny denies + if (anyDenyDenies) + { + if (denied != null) + { + for (String auth : authorisations) + { + Pair specific = new Pair(auth, required); + if (denied.contains(specific)) + { + return false; + } + for (PermissionReference perm : granters) + { + specific = new Pair(auth, perm); + if (denied.contains(specific)) + { + return false; + } + } + } + } + } + // If the permission has a match in both the authorities and // granters list it is allowed // It applies to the current user and it is granted @@ -1837,6 +1953,21 @@ public class PermissionServiceImpl extends AbstractLifecycleBean implements Perm return false; } + if(anyDenyDenies) + { + Set> allowed = new HashSet>(); + + // Check if each permission allows - the first wins. + // We could have other voting style mechanisms here + for (AccessControlEntry ace : acl.getEntries()) + { + if (isDenied(ace, authorisations, allowed, context)) + { + return false; + } + } + } + Set> denied = new HashSet>(); // Check if each permission allows - the first wins. @@ -1910,8 +2041,6 @@ public class PermissionServiceImpl extends AbstractLifecycleBean implements Perm } } - // any deny denies - // If the permission has a match in both the authorities and // granters list it is allowed // It applies to the current user and it is granted @@ -1926,6 +2055,81 @@ public class PermissionServiceImpl extends AbstractLifecycleBean implements Perm return false; } + /** + * Is a permission granted + * + * @param pe - + * the permissions entry to consider + * @param granters - + * the set of granters + * @param authorisations - + * the set of authorities + * @param denied - + * the set of denied permissions/authority pais + * @return true if granted + */ + private boolean isDenied(AccessControlEntry ace, Set authorisations, Set> allowed, PermissionContext context) + { + // If the permission entry denies then we just deny + if (ace.getAccessStatus() == AccessStatus.ALLOWED) + { + allowed.add(new Pair(ace.getAuthority(), ace.getPermission())); + + Set granters = modelDAO.getGrantingPermissions(ace.getPermission()); + for (PermissionReference granter : granters) + { + allowed.add(new Pair(ace.getAuthority(), granter)); + } + + // All the things granted by this permission must be + // denied + Set grantees = modelDAO.getGranteePermissions(ace.getPermission()); + for (PermissionReference grantee : grantees) + { + allowed.add(new Pair(ace.getAuthority(), grantee)); + } + + // All permission excludes all permissions available for + // the node. + if (ace.getPermission().equals(getAllPermissionReference()) || ace.getPermission().equals(OLD_ALL_PERMISSIONS_REFERENCE)) + { + for (PermissionReference deny : modelDAO.getAllPermissions(context.getType(), context.getAspects())) + { + allowed.add(new Pair(ace.getAuthority(), deny)); + } + } + + return false; + } + + // The permission is denied but we allow it as it is in the allowed + // set + + if (allowed != null) + { + Pair specific = new Pair(ace.getAuthority(), required); + if (allowed.contains(specific)) + { + return false; + } + } + + + // If the permission has a match in both the authorities and + // granters list it is allowed + // It applies to the current user and it is granted + if (authorisations.contains(ace.getAuthority()) && granters.contains(ace.getPermission())) + { + { + return true; + } + } + + // Default allow + return false; + } + + private boolean isGranted(PermissionEntry pe, Set authorisations) { // If the permission entry denies then we just deny @@ -2177,31 +2381,6 @@ public class PermissionServiceImpl extends AbstractLifecycleBean implements Perm } } - // any deny denies - -// if (false) -// { -// if (denied != null) -// { -// for (String auth : authorisations) -// { -// Pair specific = new Pair(auth, required); -// if (denied.contains(specific)) -// { -// return false; -// } -// for (PermissionReference perm : granters) -// { -// specific = new Pair(auth, perm); -// if (denied.contains(specific)) -// { -// return false; -// } -// } -// } -// } -// } - // If the permission has a match in both the authorities and // granters list it is allowed // It applies to the current user and it is granted @@ -2239,6 +2418,271 @@ public class PermissionServiceImpl extends AbstractLifecycleBean implements Perm } } + /** + * Ignores type and aspect requirements on the node + * + */ + private class UnconditionalDeniedAclTest + { + /* + * The required permission. + */ + PermissionReference required; + + /* + * Granters of the permission + */ + Set granters; + + /* + * The additional permissions required at the node level. + */ + Set nodeRequirements = new HashSet(); + + /* + * Constructor just gets the additional requirements + */ + UnconditionalDeniedAclTest(PermissionReference required) + { + this.required = required; + + // Set the required node permissions + if (required.equals(getPermissionReference(ALL_PERMISSIONS))) + { + nodeRequirements = modelDAO.getUnconditionalRequiredPermissions(getPermissionReference(PermissionService.FULL_CONTROL), RequiredPermission.On.NODE); + } + else + { + nodeRequirements = modelDAO.getUnconditionalRequiredPermissions(required, RequiredPermission.On.NODE); + } + + if (modelDAO.getUnconditionalRequiredPermissions(required, RequiredPermission.On.PARENT).size() > 0) + { + throw new IllegalStateException("Parent permissions can not be checked for an acl"); + } + + if (modelDAO.getUnconditionalRequiredPermissions(required, RequiredPermission.On.CHILDREN).size() > 0) + { + throw new IllegalStateException("Child permissions can not be checked for an acl"); + } + + // Find all the permissions that grant the allowed permission + // All permissions are treated specially. + granters = new LinkedHashSet(128, 1.0f); + granters.addAll(modelDAO.getGrantingPermissions(required)); + granters.add(getAllPermissionReference()); + granters.add(OLD_ALL_PERMISSIONS_REFERENCE); + } + + /** + * Internal hook point for recursion + * + * @param authorisations + * @param nodeRef + * @param denied + * @param recursiveIn + * @return true if granted + */ + boolean evaluate(String authority, Long aclId) + { + // Start out true and "and" all other results + boolean success = true; + + // Check the required permissions but not for sets they rely on + // their underlying permissions + //if (modelDAO.checkPermission(required)) + //{ + + // We have to do the test as no parent will help us out + success &= hasSinglePermission(authority, aclId); + + if (!success) + { + return false; + } + //} + + // Check the other permissions required on the node + for (PermissionReference pr : nodeRequirements) + { + // Build a new test + UnconditionalDeniedAclTest nt = new UnconditionalDeniedAclTest(pr); + success &= nt.evaluate(authority, aclId); + if (!success) + { + return false; + } + } + + return success; + } + + boolean hasSinglePermission(String authority, Long aclId) + { + // Check global permission + + if (checkGlobalPermissions(authority)) + { + return true; + } + + if(aclId == null) + { + return false; + } + else + { + return checkRequired(authority, aclId); + } + + } + + /** + * Check if we have a global permission + * + * @param authorisations + * @return true if granted + */ + private boolean checkGlobalPermissions(String authority) + { + for (PermissionEntry pe : modelDAO.getGlobalPermissionEntries()) + { + if (isDenied(pe, authority)) + { + return true; + } + } + return false; + } + + /** + * Check that a given authentication is available on a node + * + * @param authorisations + * @param nodeRef + * @param denied + * @return true if a check is required + */ + boolean checkRequired(String authority, Long aclId) + { + AccessControlList acl = aclDaoComponent.getAccessControlList(aclId); + + if (acl == null) + { + return false; + } + + Set> allowed = new HashSet>(); + + // Check if each permission allows - the first wins. + // We could have other voting style mechanisms here + for (AccessControlEntry ace : acl.getEntries()) + { + if (isDenied(ace, authority, allowed)) + { + return true; + } + } + return false; + } + + /** + * Is a permission granted + * + * @param pe - + * the permissions entry to consider + * @param granters - + * the set of granters + * @param authorisations - + * the set of authorities + * @param denied - + * the set of denied permissions/authority pais + * @return true if granted + */ + private boolean isDenied(AccessControlEntry ace, String authority, Set> allowed) + { + // If the permission entry denies then we just deny + if (ace.getAccessStatus() == AccessStatus.ALLOWED) + { + allowed.add(new Pair(ace.getAuthority(), ace.getPermission())); + + Set granters = modelDAO.getGrantingPermissions(ace.getPermission()); + for (PermissionReference granter : granters) + { + allowed.add(new Pair(ace.getAuthority(), granter)); + } + + // All the things granted by this permission must be + // denied + Set grantees = modelDAO.getGranteePermissions(ace.getPermission()); + for (PermissionReference grantee : grantees) + { + allowed.add(new Pair(ace.getAuthority(), grantee)); + } + + // All permission excludes all permissions available for + // the node. + if (ace.getPermission().equals(getAllPermissionReference()) || ace.getPermission().equals(OLD_ALL_PERMISSIONS_REFERENCE)) + { + for (PermissionReference deny : modelDAO.getAllPermissions()) + { + allowed.add(new Pair(ace.getAuthority(), deny)); + } + } + + return false; + } + + // The permission is allowed but we deny it as it is in the denied + // set + + if (allowed != null) + { + Pair specific = new Pair(ace.getAuthority(), required); + if (allowed.contains(specific)) + { + return false; + } + } + + // If the permission has a match in both the authorities and + // granters list it is allowed + // It applies to the current user and it is granted + if (authority.equals(ace.getAuthority()) && granters.contains(ace.getPermission())) + { + { + return true; + } + } + + // Default deny + return false; + } + + private boolean isDenied(PermissionEntry pe, String authority) + { + // If the permission entry denies then we just deny + if (pe.isAllowed()) + { + return false; + } + + // If the permission has a match in both the authorities and + // granters list it is allowed + // It applies to the current user and it is granted + if (granters.contains(pe.getPermissionReference()) && authority.equals(pe.getAuthority())) + { + { + return true; + } + } + + // Default deny + return false; + } + } + + private static class MutableBoolean { private boolean value; diff --git a/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceTest.java b/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceTest.java index 71476629fd..e48fd052a9 100644 --- a/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceTest.java +++ b/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceTest.java @@ -33,6 +33,7 @@ import org.alfresco.repo.security.permissions.ACLType; import org.alfresco.repo.security.permissions.AccessControlEntry; import org.alfresco.repo.security.permissions.AccessDeniedException; import org.alfresco.repo.security.permissions.PermissionEntry; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.security.AccessPermission; @@ -63,6 +64,288 @@ public class PermissionServiceTest extends AbstractPermissionTest // TODO Auto-generated constructor stub } + public void testAnyDenyDeniesAndRead() + { + personService.getPerson("andy"); + runAs("admin"); + + authorityService.createAuthority(AuthorityType.GROUP, "ONE"); + authorityService.addAuthority("GROUP_ONE", "andy"); + + authorityService.createAuthority(AuthorityType.GROUP, "TWO"); + authorityService.addAuthority("GROUP_TWO", "andy"); + + authorityService.createAuthority(AuthorityType.GROUP, "THREE"); + authorityService.addAuthority("GROUP_THREE", "andy"); + + authorityService.createAuthority(AuthorityType.GROUP, "GEN"); + authorityService.addAuthority("GROUP_GEN", "andy"); + + NodeRef one = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}one"), ContentModel.TYPE_FOLDER).getChildRef(); + permissionService.setPermission(one, "andy", PermissionService.READ, true); + permissionService.setPermission(one, "GROUP_ONE", PermissionService.READ, true); + + NodeRef two = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}two"), ContentModel.TYPE_FOLDER).getChildRef(); + permissionService.setPermission(two, "andy", PermissionService.READ, true); + permissionService.setPermission(two, "GROUP_TWO", PermissionService.READ, true); + + NodeRef three = nodeService.createNode(one, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}three"), ContentModel.TYPE_FOLDER).getChildRef(); + permissionService.setPermission(three, "andy", PermissionService.READ, true); + permissionService.setPermission(three, "GROUP_THREE", PermissionService.READ, true); + + NodeRef four = nodeService.createNode(three, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}four"), ContentModel.TYPE_FOLDER).getChildRef(); + + permissionServiceImpl.setAnyDenyDenies(false); + try + { + + + runAs("andy"); + assertEquals("andy", authenticationComponent.getCurrentUserName()); + assertTrue(permissionService.hasPermission(one, PermissionService.READ) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(two, PermissionService.READ) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(three, PermissionService.READ) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(four, PermissionService.READ) == AccessStatus.ALLOWED); + + assertTrue(permissionService.hasReadPermission(one) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasReadPermission(two) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasReadPermission(three) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasReadPermission(four) == AccessStatus.ALLOWED); + + // Deny group on one + runAs("admin"); + permissionService.setPermission(one, "GROUP_ONE", PermissionService.READ, false); + + runAs("andy"); + assertEquals("andy", authenticationComponent.getCurrentUserName()); + assertTrue(permissionService.hasPermission(one, PermissionService.READ) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(two, PermissionService.READ) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(three, PermissionService.READ) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(four, PermissionService.READ) == AccessStatus.ALLOWED); + + assertTrue(permissionService.hasReadPermission(one) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasReadPermission(two) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasReadPermission(three) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasReadPermission(four) == AccessStatus.ALLOWED); + + } + finally + { + // ANY DENY DENIES + + permissionServiceImpl.setAnyDenyDenies(true); + } + + runAs("andy"); + assertEquals("andy", authenticationComponent.getCurrentUserName()); + assertTrue(permissionService.hasPermission(one, PermissionService.READ) == AccessStatus.DENIED); + assertTrue(permissionService.hasPermission(two, PermissionService.READ) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(three, PermissionService.READ) == AccessStatus.DENIED); + assertTrue(permissionService.hasPermission(four, PermissionService.READ) == AccessStatus.DENIED); + + assertTrue(permissionService.hasReadPermission(one) == AccessStatus.DENIED); + assertTrue(permissionService.hasReadPermission(two) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasReadPermission(three) == AccessStatus.DENIED); + assertTrue(permissionService.hasReadPermission(four) == AccessStatus.DENIED); + + runAs("admin"); + permissionService.deletePermission(one, "GROUP_ONE", PermissionService.READ); + permissionService.setPermission(one, "GROUP_ONE", PermissionService.READ, true); + + // deny andy on one but explicitly allowed on three + + runAs("admin"); + permissionService.setPermission(one, "andy", PermissionService.READ, false); + + + runAs("andy"); + assertEquals("andy", authenticationComponent.getCurrentUserName()); + assertTrue(permissionService.hasPermission(one, PermissionService.READ) == AccessStatus.DENIED); + assertTrue(permissionService.hasPermission(two, PermissionService.READ) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(three, PermissionService.READ) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(four, PermissionService.READ) == AccessStatus.ALLOWED); + + assertTrue(permissionService.hasReadPermission(one) == AccessStatus.DENIED); + assertTrue(permissionService.hasReadPermission(two) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasReadPermission(three) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasReadPermission(four) == AccessStatus.ALLOWED); + + runAs("admin"); + permissionService.deletePermission(one, "andy", PermissionService.READ); + permissionService.setPermission(one, "andy", PermissionService.READ, true); + + // Deny by all - the underlying allow should win + + runAs("admin"); + permissionService.setPermission(one, "andy", PermissionService.ALL_PERMISSIONS, false); + + runAs("andy"); + assertEquals("andy", authenticationComponent.getCurrentUserName()); + assertTrue(permissionService.hasPermission(one, PermissionService.READ) == AccessStatus.DENIED); + assertTrue(permissionService.hasPermission(two, PermissionService.READ) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(three, PermissionService.READ) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(four, PermissionService.READ) == AccessStatus.ALLOWED); + + assertTrue(permissionService.hasReadPermission(one) == AccessStatus.DENIED); + assertTrue(permissionService.hasReadPermission(two) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasReadPermission(three) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasReadPermission(four) == AccessStatus.ALLOWED); + + runAs("admin"); + permissionService.deletePermission(one, "andy", PermissionService.ALL_PERMISSIONS); + + // Deny by all - should win as no underlying mask + + runAs("admin"); + permissionService.setPermission(one, "GROUP_GEN", PermissionService.ALL_PERMISSIONS, false); + + runAs("andy"); + assertEquals("andy", authenticationComponent.getCurrentUserName()); + assertTrue(permissionService.hasPermission(one, PermissionService.READ) == AccessStatus.DENIED); + assertTrue(permissionService.hasPermission(two, PermissionService.READ) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(three, PermissionService.READ) == AccessStatus.DENIED); + assertTrue(permissionService.hasPermission(four, PermissionService.READ) == AccessStatus.DENIED); + + assertTrue(permissionService.hasReadPermission(one) == AccessStatus.DENIED); + assertTrue(permissionService.hasReadPermission(two) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasReadPermission(three) == AccessStatus.DENIED); + assertTrue(permissionService.hasReadPermission(four) == AccessStatus.DENIED); + + runAs("admin"); + permissionService.setInheritParentPermissions(three, false); + + runAs("andy"); + assertEquals("andy", authenticationComponent.getCurrentUserName()); + assertTrue(permissionService.hasPermission(one, PermissionService.READ) == AccessStatus.DENIED); + assertTrue(permissionService.hasPermission(two, PermissionService.READ) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(three, PermissionService.READ) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(four, PermissionService.READ) == AccessStatus.ALLOWED); + + assertTrue(permissionService.hasReadPermission(one) == AccessStatus.DENIED); + assertTrue(permissionService.hasReadPermission(two) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasReadPermission(three) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasReadPermission(four) == AccessStatus.ALLOWED); + + runAs("admin"); + permissionService.setInheritParentPermissions(three, true); + permissionService.deletePermission(one, "GROUP_GEN", PermissionService.ALL_PERMISSIONS); + + // direct deny + + runAs("admin"); + permissionService.setPermission(three, "andy", PermissionService.READ, false); + + runAs("andy"); + assertEquals("andy", authenticationComponent.getCurrentUserName()); + assertTrue(permissionService.hasPermission(one, PermissionService.READ) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(two, PermissionService.READ) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(three, PermissionService.READ) == AccessStatus.DENIED); + assertTrue(permissionService.hasPermission(four, PermissionService.READ) == AccessStatus.DENIED); + + assertTrue(permissionService.hasReadPermission(one) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasReadPermission(two) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasReadPermission(three) == AccessStatus.DENIED); + assertTrue(permissionService.hasReadPermission(four) == AccessStatus.DENIED); + + runAs("admin"); + permissionService.deletePermission(three, "andy", PermissionService.READ); + permissionService.setPermission(three, "andy", PermissionService.READ, true); + + permissionService.setPermission(one, "GROUP_ONE", PermissionService.READ, false); + permissionService.setPermission(two, "GROUP_TWO", PermissionService.READ, false); + permissionService.setPermission(three, "GROUP_THREE", PermissionService.READ, false); + + runAs("andy"); + assertEquals("andy", authenticationComponent.getCurrentUserName()); + assertTrue(permissionService.hasPermission(one, PermissionService.READ) == AccessStatus.DENIED); + assertTrue(permissionService.hasPermission(two, PermissionService.READ) == AccessStatus.DENIED); + assertTrue(permissionService.hasPermission(three, PermissionService.READ) == AccessStatus.DENIED); + assertTrue(permissionService.hasPermission(four, PermissionService.READ) == AccessStatus.DENIED); + + assertTrue(permissionService.hasReadPermission(one) == AccessStatus.DENIED); + assertTrue(permissionService.hasReadPermission(two) == AccessStatus.DENIED); + assertTrue(permissionService.hasReadPermission(three) == AccessStatus.DENIED); + assertTrue(permissionService.hasReadPermission(four) == AccessStatus.DENIED); + + runAs("admin"); + authorityService.removeAuthority("GROUP_ONE", "andy"); + AlfrescoTransactionSupport.bindResource("MyAuthCache", null); + + runAs("andy"); + assertEquals("andy", authenticationComponent.getCurrentUserName()); + assertTrue(permissionService.hasPermission(one, PermissionService.READ) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(two, PermissionService.READ) == AccessStatus.DENIED); + assertTrue(permissionService.hasPermission(three, PermissionService.READ) == AccessStatus.DENIED); + assertTrue(permissionService.hasPermission(four, PermissionService.READ) == AccessStatus.DENIED); + + assertTrue(permissionService.hasReadPermission(one) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasReadPermission(two) == AccessStatus.DENIED); + assertTrue(permissionService.hasReadPermission(three) == AccessStatus.DENIED); + assertTrue(permissionService.hasReadPermission(four) == AccessStatus.DENIED); + + runAs("admin"); + authorityService.removeAuthority("GROUP_TWO", "andy"); + AlfrescoTransactionSupport.bindResource("MyAuthCache", null); + + runAs("andy"); + assertEquals("andy", authenticationComponent.getCurrentUserName()); + assertTrue(permissionService.hasPermission(one, PermissionService.READ) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(two, PermissionService.READ) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(three, PermissionService.READ) == AccessStatus.DENIED); + assertTrue(permissionService.hasPermission(four, PermissionService.READ) == AccessStatus.DENIED); + + assertTrue(permissionService.hasReadPermission(one) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasReadPermission(two) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasReadPermission(three) == AccessStatus.DENIED); + assertTrue(permissionService.hasReadPermission(four) == AccessStatus.DENIED); + + runAs("admin"); + authorityService.removeAuthority("GROUP_THREE", "andy"); + AlfrescoTransactionSupport.bindResource("MyAuthCache", null); + + runAs("andy"); + assertEquals("andy", authenticationComponent.getCurrentUserName()); + assertTrue(permissionService.hasPermission(one, PermissionService.READ) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(two, PermissionService.READ) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(three, PermissionService.READ) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(four, PermissionService.READ) == AccessStatus.ALLOWED); + + assertTrue(permissionService.hasReadPermission(one) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasReadPermission(two) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasReadPermission(three) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasReadPermission(four) == AccessStatus.ALLOWED); + + runAs("admin"); + permissionService.setPermission(one, PermissionService.ALL_AUTHORITIES, PermissionService.READ, false); + + runAs("andy"); + assertEquals("andy", authenticationComponent.getCurrentUserName()); + assertTrue(permissionService.hasPermission(one, PermissionService.READ) == AccessStatus.DENIED); + assertTrue(permissionService.hasPermission(two, PermissionService.READ) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(three, PermissionService.READ) == AccessStatus.DENIED); + assertTrue(permissionService.hasPermission(four, PermissionService.READ) == AccessStatus.DENIED); + + assertTrue(permissionService.hasReadPermission(one) == AccessStatus.DENIED); + assertTrue(permissionService.hasReadPermission(two) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasReadPermission(three) == AccessStatus.DENIED); + assertTrue(permissionService.hasReadPermission(four) == AccessStatus.DENIED); + + runAs("admin"); + permissionService.setPermission(rootNodeRef, PermissionService.ALL_AUTHORITIES, PermissionService.READ, false); + + runAs("andy"); + assertEquals("andy", authenticationComponent.getCurrentUserName()); + assertTrue(permissionService.hasPermission(one, PermissionService.READ) == AccessStatus.DENIED); + assertTrue(permissionService.hasPermission(two, PermissionService.READ) == AccessStatus.DENIED); + assertTrue(permissionService.hasPermission(three, PermissionService.READ) == AccessStatus.DENIED); + assertTrue(permissionService.hasPermission(four, PermissionService.READ) == AccessStatus.DENIED); + + assertTrue(permissionService.hasReadPermission(one) == AccessStatus.DENIED); + assertTrue(permissionService.hasReadPermission(two) == AccessStatus.DENIED); + assertTrue(permissionService.hasReadPermission(three) == AccessStatus.DENIED); + assertTrue(permissionService.hasReadPermission(four) == AccessStatus.DENIED); + } + /* * Tests that the current user is contained in the current authorisations set */ @@ -83,243 +366,243 @@ public class PermissionServiceTest extends AbstractPermissionTest NodeRef two = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}two"), ContentModel.TYPE_FOLDER).getChildRef(); permissionService.setPermission(two, "andy", PermissionService.WRITE, true); NodeRef three = nodeService.createNode(one, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}two"), ContentModel.TYPE_FOLDER).getChildRef(); - - + + NodeRef test = nodeService.createNode(one, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}test"), ContentModel.TYPE_FOLDER).getChildRef(); - + // test has shared acl - - + + // under 1 // start runAs("andy"); assertEquals("andy", authenticationComponent.getCurrentUserName()); assertTrue(permissionService.hasPermission(test, PermissionService.READ) == AccessStatus.ALLOWED); assertTrue(permissionService.hasPermission(test, PermissionService.WRITE) == AccessStatus.DENIED); - + //under 2 // def parent -> def parent runAs("admin"); nodeService.moveNode(test, two, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}test")); assertEquals(two, nodeService.getPrimaryParent(test).getParentRef()); - + runAs("andy"); assertEquals("andy", authenticationComponent.getCurrentUserName()); assertTrue(permissionService.hasPermission(test, PermissionService.READ) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(test, PermissionService.WRITE) == AccessStatus.ALLOWED); - + // under 3 // def parent -> shared parent runAs("admin"); nodeService.moveNode(test, three, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}test")); assertEquals(three, nodeService.getPrimaryParent(test).getParentRef()); - + runAs("andy"); assertEquals("andy", authenticationComponent.getCurrentUserName()); assertTrue(permissionService.hasPermission(test, PermissionService.READ) == AccessStatus.ALLOWED); assertTrue(permissionService.hasPermission(test, PermissionService.WRITE) == AccessStatus.DENIED); - + //under 2 // shared parent -> def parent runAs("admin"); nodeService.moveNode(test, two, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}test")); assertEquals(two, nodeService.getPrimaryParent(test).getParentRef()); - + runAs("andy"); assertEquals("andy", authenticationComponent.getCurrentUserName()); assertTrue(permissionService.hasPermission(test, PermissionService.READ) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(test, PermissionService.WRITE) == AccessStatus.ALLOWED); - + //under 1 // def parent -> def parent runAs("admin"); nodeService.moveNode(test, one, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}test")); assertEquals(one, nodeService.getPrimaryParent(test).getParentRef()); - + runAs("andy"); assertEquals("andy", authenticationComponent.getCurrentUserName()); assertTrue(permissionService.hasPermission(test, PermissionService.READ) == AccessStatus.ALLOWED); assertTrue(permissionService.hasPermission(test, PermissionService.WRITE) == AccessStatus.DENIED); - - + + // test has defining acl - + runAs("admin"); permissionService.setPermission(test, "andy", PermissionService.CHANGE_PERMISSIONS, true); - + runAs("andy"); assertEquals("andy", authenticationComponent.getCurrentUserName()); assertTrue(permissionService.hasPermission(test, PermissionService.READ) == AccessStatus.ALLOWED); assertTrue(permissionService.hasPermission(test, PermissionService.WRITE) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(test, PermissionService.CHANGE_PERMISSIONS) == AccessStatus.ALLOWED); - + // 2 runAs("admin"); nodeService.moveNode(test, two, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}test")); assertEquals(two, nodeService.getPrimaryParent(test).getParentRef()); - + runAs("andy"); assertEquals("andy", authenticationComponent.getCurrentUserName()); assertTrue(permissionService.hasPermission(test, PermissionService.READ) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(test, PermissionService.WRITE) == AccessStatus.ALLOWED); assertTrue(permissionService.hasPermission(test, PermissionService.CHANGE_PERMISSIONS) == AccessStatus.ALLOWED); - + // 3 runAs("admin"); nodeService.moveNode(test, three, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}test")); assertEquals(three, nodeService.getPrimaryParent(test).getParentRef()); - + runAs("andy"); assertEquals("andy", authenticationComponent.getCurrentUserName()); assertTrue(permissionService.hasPermission(test, PermissionService.READ) == AccessStatus.ALLOWED); assertTrue(permissionService.hasPermission(test, PermissionService.WRITE) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(test, PermissionService.CHANGE_PERMISSIONS) == AccessStatus.ALLOWED); - + // 2 runAs("admin"); nodeService.moveNode(test, two, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}test")); assertEquals(two, nodeService.getPrimaryParent(test).getParentRef()); - + runAs("andy"); assertEquals("andy", authenticationComponent.getCurrentUserName()); assertTrue(permissionService.hasPermission(test, PermissionService.READ) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(test, PermissionService.WRITE) == AccessStatus.ALLOWED); assertTrue(permissionService.hasPermission(test, PermissionService.CHANGE_PERMISSIONS) == AccessStatus.ALLOWED); - + // 1 runAs("admin"); nodeService.moveNode(test, one, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}test")); assertEquals(one, nodeService.getPrimaryParent(test).getParentRef()); - + runAs("andy"); assertEquals("andy", authenticationComponent.getCurrentUserName()); assertTrue(permissionService.hasPermission(test, PermissionService.READ) == AccessStatus.ALLOWED); assertTrue(permissionService.hasPermission(test, PermissionService.WRITE) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(test, PermissionService.CHANGE_PERMISSIONS) == AccessStatus.ALLOWED); - + // 1 inherit - + runAs("admin"); permissionService.setInheritParentPermissions(test, true); - + runAs("andy"); assertEquals("andy", authenticationComponent.getCurrentUserName()); assertTrue(permissionService.hasPermission(test, PermissionService.READ) == AccessStatus.ALLOWED); assertTrue(permissionService.hasPermission(test, PermissionService.WRITE) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(test, PermissionService.CHANGE_PERMISSIONS) == AccessStatus.ALLOWED); - + runAs("admin"); permissionService.setInheritParentPermissions(test, false); - + runAs("andy"); assertEquals("andy", authenticationComponent.getCurrentUserName()); assertTrue(permissionService.hasPermission(test, PermissionService.READ) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(test, PermissionService.WRITE) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(test, PermissionService.CHANGE_PERMISSIONS) == AccessStatus.ALLOWED); - + runAs("admin"); permissionService.setInheritParentPermissions(test, true); - + runAs("andy"); assertEquals("andy", authenticationComponent.getCurrentUserName()); assertTrue(permissionService.hasPermission(test, PermissionService.READ) == AccessStatus.ALLOWED); assertTrue(permissionService.hasPermission(test, PermissionService.WRITE) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(test, PermissionService.CHANGE_PERMISSIONS) == AccessStatus.ALLOWED); - + // 2 inherit - + runAs("admin"); nodeService.moveNode(test, two, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}test")); assertEquals(two, nodeService.getPrimaryParent(test).getParentRef()); - + runAs("andy"); assertEquals("andy", authenticationComponent.getCurrentUserName()); assertTrue(permissionService.hasPermission(test, PermissionService.READ) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(test, PermissionService.WRITE) == AccessStatus.ALLOWED); assertTrue(permissionService.hasPermission(test, PermissionService.CHANGE_PERMISSIONS) == AccessStatus.ALLOWED); - - + + runAs("admin"); permissionService.setInheritParentPermissions(test, true); - + runAs("andy"); assertEquals("andy", authenticationComponent.getCurrentUserName()); assertTrue(permissionService.hasPermission(test, PermissionService.READ) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(test, PermissionService.WRITE) == AccessStatus.ALLOWED); assertTrue(permissionService.hasPermission(test, PermissionService.CHANGE_PERMISSIONS) == AccessStatus.ALLOWED); - + runAs("admin"); permissionService.setInheritParentPermissions(test, false); - + runAs("andy"); assertEquals("andy", authenticationComponent.getCurrentUserName()); assertTrue(permissionService.hasPermission(test, PermissionService.READ) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(test, PermissionService.WRITE) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(test, PermissionService.CHANGE_PERMISSIONS) == AccessStatus.ALLOWED); - + runAs("admin"); permissionService.setInheritParentPermissions(test, true); - + runAs("andy"); assertEquals("andy", authenticationComponent.getCurrentUserName()); assertTrue(permissionService.hasPermission(test, PermissionService.READ) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(test, PermissionService.WRITE) == AccessStatus.ALLOWED); assertTrue(permissionService.hasPermission(test, PermissionService.CHANGE_PERMISSIONS) == AccessStatus.ALLOWED); - + // 3 inherit - + runAs("admin"); nodeService.moveNode(test, three, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}test")); assertEquals(three, nodeService.getPrimaryParent(test).getParentRef()); - + runAs("andy"); assertEquals("andy", authenticationComponent.getCurrentUserName()); assertTrue(permissionService.hasPermission(test, PermissionService.READ) == AccessStatus.ALLOWED); assertTrue(permissionService.hasPermission(test, PermissionService.WRITE) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(test, PermissionService.CHANGE_PERMISSIONS) == AccessStatus.ALLOWED); - + runAs("admin"); permissionService.setInheritParentPermissions(test, true); - + runAs("andy"); assertEquals("andy", authenticationComponent.getCurrentUserName()); assertTrue(permissionService.hasPermission(test, PermissionService.READ) == AccessStatus.ALLOWED); assertTrue(permissionService.hasPermission(test, PermissionService.WRITE) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(test, PermissionService.CHANGE_PERMISSIONS) == AccessStatus.ALLOWED); - + runAs("admin"); permissionService.setInheritParentPermissions(test, false); - + runAs("andy"); assertEquals("andy", authenticationComponent.getCurrentUserName()); assertTrue(permissionService.hasPermission(test, PermissionService.READ) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(test, PermissionService.WRITE) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(test, PermissionService.CHANGE_PERMISSIONS) == AccessStatus.ALLOWED); - + // move to 2 without inherit - + runAs("admin"); nodeService.moveNode(test, two, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}test")); assertEquals(two, nodeService.getPrimaryParent(test).getParentRef()); - + runAs("andy"); assertEquals("andy", authenticationComponent.getCurrentUserName()); assertTrue(permissionService.hasPermission(test, PermissionService.READ) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(test, PermissionService.WRITE) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(test, PermissionService.CHANGE_PERMISSIONS) == AccessStatus.ALLOWED); - + // move to 3 without inherit - + runAs("admin"); nodeService.moveNode(test, three, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}test")); assertEquals(three, nodeService.getPrimaryParent(test).getParentRef()); - + runAs("andy"); assertEquals("andy", authenticationComponent.getCurrentUserName()); assertTrue(permissionService.hasPermission(test, PermissionService.READ) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(test, PermissionService.WRITE) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(test, PermissionService.CHANGE_PERMISSIONS) == AccessStatus.ALLOWED); - + } public void testChangePersonUid() @@ -357,7 +640,7 @@ public class PermissionServiceTest extends AbstractPermissionTest } } assertTrue(found); - + try { nodeService.setProperty(andy, ContentModel.PROP_USERNAME, "Bob"); @@ -708,7 +991,7 @@ public class PermissionServiceTest extends AbstractPermissionTest assertEquals("andy", AuthenticationUtil.getRunAsUser()); AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() - { + { public Object doWork() throws Exception { assertEquals("andy", AuthenticationUtil.getFullyAuthenticatedUser()); @@ -720,7 +1003,7 @@ public class PermissionServiceTest extends AbstractPermissionTest assertEquals(AuthenticationUtil.getAdminUserName(), AuthenticationUtil.getRunAsUser()); AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() - { + { public Object doWork() throws Exception { @@ -730,7 +1013,7 @@ public class PermissionServiceTest extends AbstractPermissionTest assertEquals("lemur", AuthenticationUtil.getRunAsUser()); AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() - { + { public Object doWork() throws Exception { @@ -740,7 +1023,7 @@ public class PermissionServiceTest extends AbstractPermissionTest assertEquals(AuthenticationUtil.getAdminUserName(), AuthenticationUtil.getRunAsUser()); AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() - { + { public Object doWork() throws Exception { @@ -751,28 +1034,28 @@ public class PermissionServiceTest extends AbstractPermissionTest return null; } - }, "andy"); + }, "andy"); assertEquals("andy", AuthenticationUtil.getFullyAuthenticatedUser()); assertEquals(AuthenticationUtil.getAdminUserName(), AuthenticationUtil.getRunAsUser()); return null; } - }, AuthenticationUtil.getAdminUserName()); + }, AuthenticationUtil.getAdminUserName()); assertEquals("andy", AuthenticationUtil.getFullyAuthenticatedUser()); assertEquals("lemur", AuthenticationUtil.getRunAsUser()); return null; } - }, "lemur"); + }, "lemur"); assertEquals("andy", AuthenticationUtil.getFullyAuthenticatedUser()); assertEquals(AuthenticationUtil.getAdminUserName(), AuthenticationUtil.getRunAsUser()); return null; } - }, AuthenticationUtil.getAdminUserName()); + }, AuthenticationUtil.getAdminUserName()); assertEquals("andy", AuthenticationUtil.getFullyAuthenticatedUser()); assertEquals("andy", AuthenticationUtil.getRunAsUser()); @@ -792,7 +1075,7 @@ public class PermissionServiceTest extends AbstractPermissionTest assertEquals("andy", AuthenticationUtil.getRunAsUser()); AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() - { + { public Object doWork() throws Exception { @@ -802,7 +1085,7 @@ public class PermissionServiceTest extends AbstractPermissionTest assertEquals(AuthenticationUtil.getAdminUserName(), AuthenticationUtil.getRunAsUser()); AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() - { + { public Object doWork() throws Exception { @@ -812,7 +1095,7 @@ public class PermissionServiceTest extends AbstractPermissionTest assertEquals("lemur", AuthenticationUtil.getRunAsUser()); AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() - { + { public Object doWork() throws Exception { @@ -822,7 +1105,7 @@ public class PermissionServiceTest extends AbstractPermissionTest assertEquals(AuthenticationUtil.getAdminUserName(), AuthenticationUtil.getRunAsUser()); AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() - { + { public Object doWork() throws Exception { @@ -833,28 +1116,28 @@ public class PermissionServiceTest extends AbstractPermissionTest return null; } - }, "andy"); + }, "andy"); assertEquals("andy", AuthenticationUtil.getFullyAuthenticatedUser()); assertEquals(AuthenticationUtil.getAdminUserName(), AuthenticationUtil.getRunAsUser()); return null; } - }, AuthenticationUtil.getAdminUserName()); + }, AuthenticationUtil.getAdminUserName()); assertEquals("andy", AuthenticationUtil.getFullyAuthenticatedUser()); assertEquals("lemur", AuthenticationUtil.getRunAsUser()); return null; } - }, "lemur"); + }, "lemur"); assertEquals("andy", AuthenticationUtil.getFullyAuthenticatedUser()); assertEquals(AuthenticationUtil.getAdminUserName(), AuthenticationUtil.getRunAsUser()); return null; } - }, AuthenticationUtil.getAdminUserName()); + }, AuthenticationUtil.getAdminUserName()); assertEquals("andy", AuthenticationUtil.getFullyAuthenticatedUser()); assertEquals("andy", AuthenticationUtil.getRunAsUser()); @@ -873,7 +1156,7 @@ public class PermissionServiceTest extends AbstractPermissionTest assertNull(AuthenticationUtil.getRunAsUser()); AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() - { + { public Object doWork() throws Exception { @@ -883,7 +1166,7 @@ public class PermissionServiceTest extends AbstractPermissionTest assertEquals(AuthenticationUtil.getAdminUserName(), AuthenticationUtil.getRunAsUser()); return null; } - }, AuthenticationUtil.getAdminUserName()); + }, AuthenticationUtil.getAdminUserName()); assertNull(AuthenticationUtil.getFullyAuthenticatedUser()); assertNull(AuthenticationUtil.getRunAsUser()); @@ -901,7 +1184,7 @@ public class PermissionServiceTest extends AbstractPermissionTest assertNull(AuthenticationUtil.getRunAsUser()); AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() - { + { public Object doWork() throws Exception { @@ -911,7 +1194,7 @@ public class PermissionServiceTest extends AbstractPermissionTest assertEquals(AuthenticationUtil.getAdminUserName(), AuthenticationUtil.getRunAsUser()); AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() - { + { public Object doWork() throws Exception { @@ -921,7 +1204,7 @@ public class PermissionServiceTest extends AbstractPermissionTest assertEquals("lemur", AuthenticationUtil.getRunAsUser()); AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() - { + { public Object doWork() throws Exception { @@ -931,7 +1214,7 @@ public class PermissionServiceTest extends AbstractPermissionTest assertEquals(AuthenticationUtil.getAdminUserName(), AuthenticationUtil.getRunAsUser()); AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() - { + { public Object doWork() throws Exception { @@ -942,28 +1225,28 @@ public class PermissionServiceTest extends AbstractPermissionTest return null; } - }, "andy"); + }, "andy"); assertEquals(AuthenticationUtil.getAdminUserName(), AuthenticationUtil.getFullyAuthenticatedUser()); assertEquals(AuthenticationUtil.getAdminUserName(), AuthenticationUtil.getRunAsUser()); return null; } - }, AuthenticationUtil.getAdminUserName()); + }, AuthenticationUtil.getAdminUserName()); assertEquals(AuthenticationUtil.getAdminUserName(), AuthenticationUtil.getFullyAuthenticatedUser()); assertEquals("lemur", AuthenticationUtil.getRunAsUser()); return null; } - }, "lemur"); + }, "lemur"); assertEquals(AuthenticationUtil.getAdminUserName(), AuthenticationUtil.getFullyAuthenticatedUser()); assertEquals(AuthenticationUtil.getAdminUserName(), AuthenticationUtil.getRunAsUser()); return null; } - }, AuthenticationUtil.getAdminUserName()); + }, AuthenticationUtil.getAdminUserName()); assertNull(AuthenticationUtil.getFullyAuthenticatedUser()); assertNull(AuthenticationUtil.getRunAsUser()); @@ -1042,7 +1325,7 @@ public class PermissionServiceTest extends AbstractPermissionTest runAs(AuthenticationUtil.getAdminUserName()); NodeRef n1 = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}one"), ContentModel.TYPE_FOLDER).getChildRef(); - + permissionService.setPermission(new SimplePermissionEntry(n1, getPermission(PermissionService.READ), "andy", AccessStatus.ALLOWED)); permissionService.setPermission(new SimplePermissionEntry(n1, getPermission(PermissionService.READ), "Andy", AccessStatus.ALLOWED)); permissionService.setPermission(new SimplePermissionEntry(n1, getPermission(PermissionService.READ), "ANDY", AccessStatus.ALLOWED)); @@ -1051,11 +1334,11 @@ public class PermissionServiceTest extends AbstractPermissionTest permissionService.setPermission(new SimplePermissionEntry(n1, getPermission(PermissionService.READ), "woof/ADOBE", AccessStatus.ALLOWED)); permissionService.setPermission(new SimplePermissionEntry(n1, getPermission(PermissionService.READ), "Woof/Adobe", AccessStatus.ALLOWED)); permissionService.setPermission(new SimplePermissionEntry(n1, getPermission(PermissionService.READ), "WOOF/ADOBE", AccessStatus.ALLOWED)); - + assertEquals(8, permissionService.getAllSetPermissions(n1).size()); } - - + + public void testGetAllSetPermissions() { runAs("andy"); @@ -1109,15 +1392,15 @@ public class PermissionServiceTest extends AbstractPermissionTest NodeRef n1 = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}one"), ContentModel.TYPE_FOLDER).getChildRef(); NodeRef n2 = nodeService.createNode(n1, ContentModel.ASSOC_CONTAINS, QName.createQName("{namespace}two"), ContentModel.TYPE_FOLDER).getChildRef(); - + runAs("andy"); - + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(n1, getPermission(PermissionService.READ)) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.DENIED); - + runAs(AuthenticationUtil.getAdminUserName()); - + permissionService.setPermission(new SimplePermissionEntry(n1, getPermission(PermissionService.READ), "andy", AccessStatus.ALLOWED)); runAs("andy"); @@ -1943,16 +2226,16 @@ public class PermissionServiceTest extends AbstractPermissionTest long start; long end; long time = 0; -// for (int i = 0; i < 1000; i++) -// { -// getSession().flush(); -// // getSession().clear(); -// start = System.nanoTime(); -// assertTrue(permissionService.hasPermission(n10, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); -// end = System.nanoTime(); -// time += (end - start); -// } -// System.out.println("Time is " + (time / 1000000000.0)); + // for (int i = 0; i < 1000; i++) + // { + // getSession().flush(); + // // getSession().clear(); + // start = System.nanoTime(); + // assertTrue(permissionService.hasPermission(n10, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + // end = System.nanoTime(); + // time += (end - start); + // } + // System.out.println("Time is " + (time / 1000000000.0)); // assertTrue((time / 1000000000.0) < 60.0); time = 0; @@ -2313,8 +2596,8 @@ public class PermissionServiceTest extends AbstractPermissionTest permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES), "andy", AccessStatus.DENIED)); runAs("andy"); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.DENIED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); runAs("lemur"); @@ -2325,8 +2608,8 @@ public class PermissionServiceTest extends AbstractPermissionTest permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ_CHILDREN), "andy", AccessStatus.ALLOWED)); runAs("andy"); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.DENIED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); runAs("lemur"); @@ -2337,10 +2620,10 @@ public class PermissionServiceTest extends AbstractPermissionTest permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ), "andy", AccessStatus.DENIED)); runAs("andy"); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.DENIED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.DENIED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.DENIED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.DENIED); runAs("lemur"); assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); @@ -2348,6 +2631,89 @@ public class PermissionServiceTest extends AbstractPermissionTest assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); } + public void testGroupAndUserInteractionAnyAllowAllows() + { + + permissionServiceImpl.setAnyDenyDenies(false); + try + { + runAs("andy"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ), "andy", AccessStatus.ALLOWED)); + runAs("andy"); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ), ROLE_AUTHENTICATED, AccessStatus.ALLOWED)); + runAs("andy"); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES), "andy", AccessStatus.DENIED)); + runAs("andy"); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ_CHILDREN), "andy", AccessStatus.ALLOWED)); + runAs("andy"); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ), "andy", AccessStatus.DENIED)); + runAs("andy"); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + } + finally + { + permissionServiceImpl.setAnyDenyDenies(true); + } + } + public void testInheritPermissions() { runAs(AuthenticationUtil.getAdminUserName()); @@ -2770,7 +3136,7 @@ public class PermissionServiceTest extends AbstractPermissionTest assertEquals(0, permissionService.getAllSetPermissions(rootNodeRef).size()); } - + public void xtestAclInsertionPerformanceShared() { NodeRef parent = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}one"), ContentModel.TYPE_FOLDER).getChildRef(); @@ -2781,10 +3147,10 @@ public class PermissionServiceTest extends AbstractPermissionTest long start = System.nanoTime(); permissionService.setPermission(new SimplePermissionEntry(parent, getPermission(PermissionService.CONSUMER), "andy", AccessStatus.ALLOWED)); long end = System.nanoTime(); - + assertTrue("Time was "+(end - start)/1000000000.0f, end == start); } - + public void xtestAclInsertionPerformanceDefining() { NodeRef parent = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}one"), ContentModel.TYPE_FOLDER).getChildRef(); @@ -2904,7 +3270,7 @@ public class PermissionServiceTest extends AbstractPermissionTest // assertEquals(1, permissionService.getAllSetPermissionsForAuthority("andy").get(n10).size()); } - + public void test_DefiningShared_AclUpdatePerformance() { runAs("admin"); @@ -2928,7 +3294,7 @@ public class PermissionServiceTest extends AbstractPermissionTest //assertTrue("Time was "+(end - start)/1000000000.0f, end == start); } - + public void test_DefiningDefining_AclUpdatePerformance() { runAs("admin"); @@ -2943,7 +3309,7 @@ public class PermissionServiceTest extends AbstractPermissionTest permissionService.setPermission(two, "andy", PermissionService.WRITE, true); NodeRef test = nodeService.createNode(one, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}test"), ContentModel.TYPE_FOLDER).getChildRef(); permissionService.setPermission(test, "andy", PermissionService.CHANGE_PERMISSIONS, true); - + // test has shared acl @@ -2956,10 +3322,10 @@ public class PermissionServiceTest extends AbstractPermissionTest long end = System.nanoTime(); //assertTrue("Time was "+(end - start)/1000000000.0f, end == start); - + } - + public void testAclInsertionPerformanceShared() { NodeRef parent = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}one"), ContentModel.TYPE_FOLDER).getChildRef(); @@ -2970,10 +3336,10 @@ public class PermissionServiceTest extends AbstractPermissionTest long start = System.nanoTime(); permissionService.setPermission(new SimplePermissionEntry(parent, getPermission(PermissionService.CONSUMER), "andy", AccessStatus.ALLOWED)); long end = System.nanoTime(); - + //assertTrue("Time was "+(end - start)/1000000000.0f, end == start); } - + public void testAclInsertionPerformanceDefining() { NodeRef parent = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}one"), ContentModel.TYPE_FOLDER).getChildRef(); @@ -3006,7 +3372,7 @@ public class PermissionServiceTest extends AbstractPermissionTest //assertTrue("Time was "+(end - start)/1000000000.0f, end == start); } - + public void xtestFindNodesByPermission() { diff --git a/source/java/org/alfresco/repo/security/permissions/impl/acegi/ACLEntryVoter.java b/source/java/org/alfresco/repo/security/permissions/impl/acegi/ACLEntryVoter.java index b59c3407c3..3397359f3e 100644 --- a/source/java/org/alfresco/repo/security/permissions/impl/acegi/ACLEntryVoter.java +++ b/source/java/org/alfresco/repo/security/permissions/impl/acegi/ACLEntryVoter.java @@ -57,6 +57,8 @@ public class ACLEntryVoter implements AccessDecisionVoter, InitializingBean private static Log log = LogFactory.getLog(ACLEntryVoter.class); private static final String ACL_NODE = "ACL_NODE"; + + private static final String ACL_PRI_CHILD_ASSOC_ON_CHILD = "ACL_PRI_CHILD_ASSOC_ON_CHILD"; private static final String ACL_PARENT = "ACL_PARENT"; @@ -205,6 +207,7 @@ public class ACLEntryVoter implements AccessDecisionVoter, InitializingBean { if ((attribute.getAttribute() != null) && (attribute.getAttribute().startsWith(ACL_NODE) + || attribute.getAttribute().startsWith(ACL_PRI_CHILD_ASSOC_ON_CHILD) || attribute.getAttribute().startsWith(ACL_PARENT) || attribute.getAttribute().equals(ACL_ALLOW) || attribute.getAttribute().startsWith(ACL_METHOD) @@ -256,7 +259,7 @@ public class ACLEntryVoter implements AccessDecisionVoter, InitializingBean MethodInvocation invocation = (MethodInvocation) object; Method method = invocation.getMethod(); - Class[] params = method.getParameterTypes(); + Class[] params = method.getParameterTypes(); Boolean hasMethodEntry = null; @@ -288,30 +291,95 @@ public class ACLEntryVoter implements AccessDecisionVoter, InitializingBean hasMethodEntry = Boolean.TRUE; } } - else if (cad.parameter >= invocation.getArguments().length) + else if (cad.typeString.equals(ACL_PRI_CHILD_ASSOC_ON_CHILD)) { - continue; + if (cad.parameter.length == 2 && NodeRef.class.isAssignableFrom(params[cad.parameter[0]]) + && NodeRef.class.isAssignableFrom(params[cad.parameter[1]])) + { + testNodeRef = getArgument(invocation, cad.parameter[1]); + if (testNodeRef != null) + { + if (nodeService.exists(testNodeRef)) + { + if (log.isDebugEnabled()) + { + log.debug("\tPermission test on node " + nodeService.getPath(testNodeRef)); + } + ChildAssociationRef primaryParent = nodeService.getPrimaryParent(testNodeRef); + NodeRef testParentNodeRef = getArgument(invocation, cad.parameter[0]); + if (primaryParent == null || testParentNodeRef == null + || !testParentNodeRef.equals(primaryParent.getParentRef())) + { + if (log.isDebugEnabled()) + { + log.debug("\tPermission test ignoring secondary parent association to " + + testParentNodeRef); + } + testNodeRef = null; + } + } + else if (log.isDebugEnabled()) + { + log.debug("\tPermission test on non-existing node " + testNodeRef); + } + } + } + else if (cad.parameter.length == 1 && ChildAssociationRef.class.isAssignableFrom(params[cad.parameter[0]])) + { + ChildAssociationRef testParentRef = getArgument(invocation, cad.parameter[0]); + if (testParentRef != null) + { + if (testParentRef.isPrimary()) + { + testNodeRef = testParentRef.getChildRef(); + if (log.isDebugEnabled()) + { + if (nodeService.exists(testNodeRef)) + { + log.debug("\tPermission test on node " + nodeService.getPath(testNodeRef)); + } + else + { + log.debug("\tPermission test on non-existing node " + testNodeRef); + } + } + } + else if (log.isDebugEnabled()) + { + log.debug("\tPermission test ignoring secondary parent association to " + + testParentRef.getParentRef()); + } + } + } + else + { + throw new ACLEntryVoterException("The specified parameter is not a NodeRef or ChildAssociationRef"); + } } else if (cad.typeString.equals(ACL_NODE)) { - if (StoreRef.class.isAssignableFrom(params[cad.parameter])) + if (cad.parameter.length != 1) { - if (invocation.getArguments()[cad.parameter] != null) + throw new ACLEntryVoterException("The specified parameter is not a NodeRef or ChildAssociationRef"); + } + else if (StoreRef.class.isAssignableFrom(params[cad.parameter[0]])) + { + StoreRef storeRef = getArgument(invocation, cad.parameter[0]); + if (storeRef != null) { if (log.isDebugEnabled()) { log.debug("\tPermission test against the store - using permissions on the root node"); } - StoreRef storeRef = (StoreRef) invocation.getArguments()[cad.parameter]; if (nodeService.exists(storeRef)) { testNodeRef = nodeService.getRootNode(storeRef); } } } - else if (NodeRef.class.isAssignableFrom(params[cad.parameter])) + else if (NodeRef.class.isAssignableFrom(params[cad.parameter[0]])) { - testNodeRef = (NodeRef) invocation.getArguments()[cad.parameter]; + testNodeRef = getArgument(invocation, cad.parameter[0]); if (log.isDebugEnabled()) { if (nodeService.exists(testNodeRef)) @@ -322,14 +390,14 @@ public class ACLEntryVoter implements AccessDecisionVoter, InitializingBean { log.debug("\tPermission test on non-existing node " +testNodeRef); } - } } - else if (ChildAssociationRef.class.isAssignableFrom(params[cad.parameter])) + else if (ChildAssociationRef.class.isAssignableFrom(params[cad.parameter[0]])) { - if (invocation.getArguments()[cad.parameter] != null) + ChildAssociationRef testChildRef = getArgument(invocation, cad.parameter[0]); + if (testChildRef != null) { - testNodeRef = ((ChildAssociationRef) invocation.getArguments()[cad.parameter]).getChildRef(); + testNodeRef = testChildRef.getChildRef(); if (log.isDebugEnabled()) { if (nodeService.exists(testNodeRef)) @@ -352,9 +420,13 @@ public class ACLEntryVoter implements AccessDecisionVoter, InitializingBean { // There is no point having parent permissions for store // refs - if (NodeRef.class.isAssignableFrom(params[cad.parameter])) + if (cad.parameter.length != 1) { - NodeRef child = (NodeRef) invocation.getArguments()[cad.parameter]; + throw new ACLEntryVoterException("The specified parameter is not a NodeRef or ChildAssociationRef"); + } + else if (NodeRef.class.isAssignableFrom(params[cad.parameter[0]])) + { + NodeRef child = getArgument(invocation, cad.parameter[0]); if (child != null) { testNodeRef = nodeService.getPrimaryParent(child).getParentRef(); @@ -372,11 +444,12 @@ public class ACLEntryVoter implements AccessDecisionVoter, InitializingBean } } } - else if (ChildAssociationRef.class.isAssignableFrom(params[cad.parameter])) + else if (ChildAssociationRef.class.isAssignableFrom(params[cad.parameter[0]])) { - if (invocation.getArguments()[cad.parameter] != null) + ChildAssociationRef testParentRef = getArgument(invocation, cad.parameter[0]); + if (testParentRef != null) { - testNodeRef = ((ChildAssociationRef) invocation.getArguments()[cad.parameter]).getParentRef(); + testNodeRef = testParentRef.getParentRef(); if (log.isDebugEnabled()) { if (nodeService.exists(testNodeRef)) @@ -456,6 +529,13 @@ public class ACLEntryVoter implements AccessDecisionVoter, InitializingBean } } + @SuppressWarnings("unchecked") + private T getArgument(MethodInvocation invocation, int index) + { + Object[] args = invocation.getArguments(); + return index > args.length ? null : (T)args[index]; + } + private List extractSupportedDefinitions(ConfigAttributeDefinition config) { List definitions = new ArrayList(2); @@ -480,7 +560,7 @@ public class ACLEntryVoter implements AccessDecisionVoter, InitializingBean SimplePermissionReference required; - int parameter; + int[] parameter; String authority; @@ -493,24 +573,36 @@ public class ACLEntryVoter implements AccessDecisionVoter, InitializingBean } typeString = st.nextToken(); - if (!(typeString.equals(ACL_NODE) || typeString.equals(ACL_PARENT) || typeString.equals(ACL_ALLOW) || typeString - .equals(ACL_METHOD) || typeString.equals(ACL_DENY))) + if (!(typeString.equals(ACL_NODE) || typeString.equals(ACL_PRI_CHILD_ASSOC_ON_CHILD) + || typeString.equals(ACL_PARENT) || typeString.equals(ACL_ALLOW) || typeString.equals(ACL_METHOD) || typeString + .equals(ACL_DENY))) { throw new ACLEntryVoterException("Invalid type: must be ACL_NODE, ACL_PARENT or ACL_ALLOW"); } - if (typeString.equals(ACL_NODE) || typeString.equals(ACL_PARENT)) + if (typeString.equals(ACL_NODE) || typeString.equals(ACL_PRI_CHILD_ASSOC_ON_CHILD) + || typeString.equals(ACL_PARENT)) { - if (st.countTokens() != 3) + int count = st.countTokens(); + if (typeString.equals(ACL_PRI_CHILD_ASSOC_ON_CHILD)) { - throw new ACLEntryVoterException("There must be four . separated tokens in each config attribute"); + if (count != 3 && count != 4) + { + throw new ACLEntryVoterException("There must be three or four . separated tokens in each config attribute"); + } + } + else if (count != 3) + { + throw new ACLEntryVoterException("There must be three . separated tokens in each config attribute"); + } + // Handle a variable number of parameters + parameter = new int[count - 2]; + for (int i=0; i(); + Set sourceAspects = nodeDAO.getNodeAspects(nodeId); + for(QName aspectQName : sourceAspects) + { + AspectDefinition aspect = dictionaryService.getAspect(aspectQName); + if(aspect != null) + { + aspects.add(aspectQName); + } + } } nodeMetaData.setAspects(aspects); @@ -593,9 +613,7 @@ public class SOLRTrackingComponentImpl implements SOLRTrackingComponent { paths.add(new Pair(catPair.getFirst().getBaseNamePath(tenantService), catPair.getSecond())); } - - - + nodeMetaData.setPaths(paths); } @@ -894,4 +912,23 @@ public class SOLRTrackingComponentImpl implements SOLRTrackingComponent nodeDAO.setCheckNodeConsistency(); return nodeDAO.getMaxTxnIdByCommitTime(maxCommitTime); } + + /* (non-Javadoc) + * @see org.alfresco.repo.solr.SOLRTrackingComponent#getMaxChangeSetCommitTime() + */ + @Override + public Long getMaxChangeSetCommitTime() + { + return aclDAO.getMaxChangeSetCommitTime(); + } + + /* (non-Javadoc) + * @see org.alfresco.repo.solr.SOLRTrackingComponent#getMaxChangeSetId() + */ + @Override + public Long getMaxChangeSetId() + { + long maxCommitTime = System.currentTimeMillis()+1L; + return aclDAO.getMaxChangeSetIdByCommitTime(maxCommitTime); + } } diff --git a/source/java/org/alfresco/repo/thumbnail/FailedThumbnailSourceAspect.java b/source/java/org/alfresco/repo/thumbnail/FailedThumbnailSourceAspect.java index 4cc4b8df50..daab686b60 100644 --- a/source/java/org/alfresco/repo/thumbnail/FailedThumbnailSourceAspect.java +++ b/source/java/org/alfresco/repo/thumbnail/FailedThumbnailSourceAspect.java @@ -30,6 +30,8 @@ import org.alfresco.repo.policy.Behaviour; import org.alfresco.repo.policy.BehaviourFilter; 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.service.cmr.lock.LockService; import org.alfresco.service.cmr.lock.LockStatus; import org.alfresco.service.cmr.repository.ChildAssociationRef; @@ -136,12 +138,20 @@ public class FailedThumbnailSourceAspect implements NodeServicePolicies.OnDelete } @Override - public void onContentUpdate(NodeRef nodeRef, boolean newContent) + public void onContentUpdate(final NodeRef nodeRef, boolean newContent) { - if (nodeService.exists(nodeRef) && lockService.getLockStatus(nodeRef) != LockStatus.LOCKED) + AuthenticationUtil.runAsSystem(new RunAsWork() { - deleteFailedThumbnailChildren(nodeRef); - } + @Override + public Object doWork() + { + if (nodeService.exists(nodeRef) && lockService.getLockStatus(nodeRef) != LockStatus.LOCKED) + { + deleteFailedThumbnailChildren(nodeRef); + } + return null; + } + }); } /** diff --git a/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java b/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java index a964ae1a92..1b0ad92cc2 100644 --- a/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java +++ b/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java @@ -73,6 +73,8 @@ 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.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.hibernate.CacheMode; import org.hibernate.Criteria; import org.hibernate.FlushMode; @@ -125,6 +127,8 @@ import org.springmodules.workflow.jbpm31.JbpmTemplate; */ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine { + private static Log logger = LogFactory.getLog(JBPMEngine.class); + // Implementation dependencies protected NodeService nodeService; protected ServiceRegistry serviceRegistry; @@ -442,7 +446,15 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine { public WorkflowDefinition apply(ProcessDefinition value) { - return createWorkflowDefinition(value); + try + { + return createWorkflowDefinition(value); + } + catch (Exception ex) + { + logger.warn("Unable to load workflow definition: '" + value + "' due to exception.", ex); + return null; + } } }); } @@ -453,7 +465,15 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine { public WorkflowInstance apply(ProcessInstance value) { - return createWorkflowInstance(value); + try + { + return createWorkflowInstance(value); + } + catch (Exception ex) + { + logger.warn("Unable to load workflow instance: '" + value + "' due to exception.", ex); + return null; + } } }); } @@ -893,7 +913,15 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine // retrieve workflow GraphSession graphSession = context.getGraphSession(); ProcessInstance processInstance = getProcessInstanceIfExists(graphSession, workflowId); - return createWorkflowInstance(processInstance); + try + { + return createWorkflowInstance(processInstance); + } + catch (Exception ex) + { + logger.warn("Unable to load workflow instance: '" + processInstance + "' due to exception.", ex); + return null; + } } }); } @@ -1104,7 +1132,14 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine 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)); + try + { + workflowInstances.add(createWorkflowInstance(processInstance)); + } + catch(Exception ex) + { + logger.warn("Unable to load workflow instance: '" + processInstance + "' due to exception.", ex); + } // delete the process instance graphSession.deleteProcessInstance(processInstance, true, true); @@ -1514,12 +1549,20 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine /// ------------------------ for(Object[] row : rows) { - WorkflowTask workflowTask = makeWorkflowTask(row, taskInstanceCache, variablesCache); - if(workflowTask !=null ) + try { - workflowTasks.add(workflowTask); + WorkflowTask workflowTask = makeWorkflowTask(row, taskInstanceCache, variablesCache); + if(workflowTask != null) + { + workflowTasks.add(workflowTask); + } } - } + catch (Exception ex) + { + logger.warn("Unable to load workflow instance: '" + row[0] + "' due to exception.", ex); + continue; + } + } return workflowTasks; } @@ -1774,8 +1817,16 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine workflowTasks = new ArrayList(filteredTasks.size()); for (TaskInstance task : filteredTasks) { - WorkflowTask workflowTask = createWorkflowTask(task); - workflowTasks.add(workflowTask); + try + { + WorkflowTask workflowTask = createWorkflowTask(task); + workflowTasks.add(workflowTask); + } + catch (Exception ex) + { + logger.warn("Unable to load workflow task: '" + task + "' due to exception.", ex); + continue; + } } } diff --git a/source/java/org/alfresco/repo/workflow/jscript/JscriptWorkflowInstance.java b/source/java/org/alfresco/repo/workflow/jscript/JscriptWorkflowInstance.java index e95a9d70f4..574cfa36e4 100644 --- a/source/java/org/alfresco/repo/workflow/jscript/JscriptWorkflowInstance.java +++ b/source/java/org/alfresco/repo/workflow/jscript/JscriptWorkflowInstance.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 * @@ -185,9 +185,19 @@ public class JscriptWorkflowInstance implements Serializable /** * Delete workflow instance + * @deprecated as 'delete' is a JavaScript reserved word and so is unusable. Use {@link #remove()} instead. */ - public void delete() - { - serviceRegistry.getWorkflowService().deleteWorkflow(this.id); - } + public void delete() + { + this.remove(); + } + + /** + * Deletes workflow instance. + * @since 3.4.9 + */ + public void remove() + { + serviceRegistry.getWorkflowService().deleteWorkflow(this.id); + } } diff --git a/source/java/org/alfresco/service/cmr/security/PersonService.java b/source/java/org/alfresco/service/cmr/security/PersonService.java index 324b28dd57..b332c2da37 100644 --- a/source/java/org/alfresco/service/cmr/security/PersonService.java +++ b/source/java/org/alfresco/service/cmr/security/PersonService.java @@ -333,4 +333,13 @@ public interface PersonService */ @NotAuditable public int countPeople(); + + /** + * Is the specified user, enabled + * @throws NoSuchPersonException + * if the user doesn't exist + * @return true = enabled. + */ + @NotAuditable + public boolean isEnabled(final String userName); } diff --git a/source/java/org/alfresco/util/DynamicallySizedThreadPoolExecutor.java b/source/java/org/alfresco/util/DynamicallySizedThreadPoolExecutor.java deleted file mode 100644 index 91a5a513c6..0000000000 --- a/source/java/org/alfresco/util/DynamicallySizedThreadPoolExecutor.java +++ /dev/null @@ -1,156 +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.util; - -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.RejectedExecutionHandler; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.ReentrantLock; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * This is an instance of {@link java.util.concurrent.ThreadPoolExecutor} which - * behaves how one would expect it to, even when faced with an unlimited - * queue. Unlike the default {@link java.util.concurrent.ThreadPoolExecutor}, it - * will add new Threads up to {@link #setMaximumPoolSize(int) maximumPoolSize} - * when there is lots of pending work, rather than only when the queue is full - * (which it often never will be, especially for unlimited queues) - * - * @author Nick Burch - */ -public class DynamicallySizedThreadPoolExecutor extends ThreadPoolExecutor -{ - private static Log logger = LogFactory.getLog(DynamicallySizedThreadPoolExecutor.class); - - private final ReentrantLock lock = new ReentrantLock(); - private int realCorePoolSize; - - public DynamicallySizedThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, - BlockingQueue workQueue, RejectedExecutionHandler handler) - { - super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler); - this.realCorePoolSize = corePoolSize; - } - - public DynamicallySizedThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, - BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) - { - super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler); - this.realCorePoolSize = corePoolSize; - } - - public DynamicallySizedThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, - BlockingQueue workQueue, ThreadFactory threadFactory) - { - super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory); - this.realCorePoolSize = corePoolSize; - } - - public DynamicallySizedThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, - BlockingQueue workQueue) - { - super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue); - this.realCorePoolSize = corePoolSize; - } - - @Override - public void setCorePoolSize(int corePoolSize) - { - this.realCorePoolSize = corePoolSize; - super.setCorePoolSize(corePoolSize); - } - - @Override - public void execute(Runnable command) - { - // Do we want to add another thread? - int threadCount = getPoolSize(); - if(logger.isDebugEnabled()) - { - logger.debug("Current pool size is " + threadCount + ", real core=" + realCorePoolSize + - ", current core=" + getCorePoolSize() + ", max=" + getMaximumPoolSize()); - } - - if(threadCount < getMaximumPoolSize()) - { - // We're not yet at the full thread count - - // Does the queue size warrant adding one? - // (If there are more than the maximum pool size of jobs pending, - // it's time to add another thread) - int queueSize = getQueue().size() + 1;// New job not yet added - if(queueSize >= getMaximumPoolSize()) - { - lock.lock(); - int currentCoreSize = getCorePoolSize(); - if(currentCoreSize < getMaximumPoolSize()) - { - super.setCorePoolSize(currentCoreSize+1); - - if(logger.isInfoEnabled()) - { - logger.info("Increased pool size to " + getCorePoolSize() + " from " + - currentCoreSize + " due to queue size of " + queueSize); - } - } - lock.unlock(); - } - } - - // Now run the actual work - super.execute(command); - } - - @Override - protected void afterExecute(Runnable r, Throwable t) - { - // If the queue is looking empty, allow the pool to - // get rid of idle threads when it wants to - int threadCount = getPoolSize(); - if(threadCount == getMaximumPoolSize() && threadCount > realCorePoolSize) - { - int queueSize = getQueue().size(); - int currentCoreSize = getCorePoolSize(); - if(queueSize < 2 && currentCoreSize > realCorePoolSize) - { - // Almost out of work, allow the pool to reduce threads when - // required. Double checks the sizing inside a lock to avoid - // race conditions taking us below the real core size. - lock.lock(); - currentCoreSize = getCorePoolSize(); - if(currentCoreSize > realCorePoolSize) - { - super.setCorePoolSize(currentCoreSize-1); - - if(logger.isInfoEnabled()) - { - logger.info("Decreased pool size to " + getCorePoolSize() + " from " + - currentCoreSize + " (real core size is " + realCorePoolSize + - ") due to queue size of " + queueSize); - } - } - lock.unlock(); - } - } - } -} diff --git a/source/java/org/alfresco/util/TraceableThreadFactory.java b/source/java/org/alfresco/util/TraceableThreadFactory.java deleted file mode 100644 index f0fde3372b..0000000000 --- a/source/java/org/alfresco/util/TraceableThreadFactory.java +++ /dev/null @@ -1,109 +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.util; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * A thread factory that spawns threads that are statically visible. Each factory uses a unique - * thread group. All the groups that have been used can be fetched using - * {@link #getActiveThreadGroups()}, allowing iteration of the the threads in the group. - * - * @since 2.1 - * @author Derek Hulley - */ -public class TraceableThreadFactory implements ThreadFactory -{ - private static final AtomicInteger factoryNumber = new AtomicInteger(1); - private static List activeThreadGroups = Collections.synchronizedList(new ArrayList(1)); - - /** - * Get a list of thread groups registered by the factory. - * - * @return Returns a snapshot of thread groups - */ - public static List getActiveThreadGroups() - { - return activeThreadGroups; - } - - private final ThreadGroup group; - private String namePrefix; - private final AtomicInteger threadNumber; - private boolean threadDaemon; - private int threadPriority; - - - public TraceableThreadFactory() - { - this.group = new ThreadGroup("TraceableThreadGroup-" + factoryNumber.getAndIncrement()); - TraceableThreadFactory.activeThreadGroups.add(this.group); - - this.namePrefix = "TraceableThread-" + factoryNumber.getAndIncrement() + "-thread-"; - this.threadNumber = new AtomicInteger(1); - - this.threadDaemon = true; - this.threadPriority = Thread.NORM_PRIORITY; - } - - /** - * @param daemon true if all threads created must be daemon threads - */ - public void setThreadDaemon(boolean daemon) - { - this.threadDaemon = daemon; - } - - /** - * - * @param threadPriority the threads priority from 1 (lowest) to 10 (highest) - */ - public void setThreadPriority(int threadPriority) - { - this.threadPriority = threadPriority; - } - - public Thread newThread(Runnable r) - { - Thread thread = new Thread( - group, - r, - namePrefix + threadNumber.getAndIncrement(), - 0); - thread.setDaemon(threadDaemon); - thread.setPriority(threadPriority); - - return thread; - } - - public void setNamePrefix(String namePrefix) - { - this.namePrefix = namePrefix; - } - - public String getNamePrefix() - { - return this.namePrefix; - } - -}