diff --git a/.externalToolBuilders/JibX.launch b/.externalToolBuilders/JibX.launch index 10e2d8f564..0856e8e600 100644 --- a/.externalToolBuilders/JibX.launch +++ b/.externalToolBuilders/JibX.launch @@ -1,37 +1,30 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/alfresco/application-context.xml b/config/alfresco/application-context.xml index 5b142fcc65..b291221eb9 100644 --- a/config/alfresco/application-context.xml +++ b/config/alfresco/application-context.xml @@ -22,6 +22,7 @@ + diff --git a/config/alfresco/authentication-services-context.xml b/config/alfresco/authentication-services-context.xml index 6a5c4fb0ca..fe8bfc971a 100644 --- a/config/alfresco/authentication-services-context.xml +++ b/config/alfresco/authentication-services-context.xml @@ -255,12 +255,8 @@ ${server.transaction.allow-writes} - - - - - - ${user.name.caseSensitive} + + ${user.name.caseSensitive} diff --git a/config/alfresco/bootstrap-context.xml b/config/alfresco/bootstrap-context.xml index a1b78152ca..c12b56d2f7 100644 --- a/config/alfresco/bootstrap-context.xml +++ b/config/alfresco/bootstrap-context.xml @@ -112,6 +112,10 @@ /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.templates.email.childname} alfresco/templates/email_templates.acp + + /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.templates.rss.childname} + alfresco/templates/rss_templates.acp + /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.scripts.childname} alfresco/bootstrap/example_javascripts.acp diff --git a/config/alfresco/bootstrap/descriptor.xml b/config/alfresco/bootstrap/descriptor.xml index f7836a4e8f..c4923a7c56 100644 --- a/config/alfresco/bootstrap/descriptor.xml +++ b/config/alfresco/bootstrap/descriptor.xml @@ -5,6 +5,7 @@ ${version.major} ${version.minor} ${version.revision} + ${version.build} ${version.label} ${version.schema} @@ -13,6 +14,7 @@ ${version.major} ${version.minor} ${version.revision} + ${version.build} ${version.edition} ${version.label} ${version.schema} diff --git a/config/alfresco/bootstrap/example_javascripts.acp b/config/alfresco/bootstrap/example_javascripts.acp index f954936f08..c079c9152d 100644 Binary files a/config/alfresco/bootstrap/example_javascripts.acp and b/config/alfresco/bootstrap/example_javascripts.acp differ diff --git a/config/alfresco/bootstrap/spaces.xml b/config/alfresco/bootstrap/spaces.xml index d8a593e6b5..25edba6c30 100644 --- a/config/alfresco/bootstrap/spaces.xml +++ b/config/alfresco/bootstrap/spaces.xml @@ -45,6 +45,19 @@ ${spaces.templates.email.name} ${spaces.templates.email.description} + + + + guest + Consumer + + + + ${spaces.templates.rss.name} + space-icon-default + ${spaces.templates.rss.name} + ${spaces.templates.rss.description} + diff --git a/config/alfresco/bootstrap/system.xml b/config/alfresco/bootstrap/system.xml index ea825f418e..a868e910c7 100644 --- a/config/alfresco/bootstrap/system.xml +++ b/config/alfresco/bootstrap/system.xml @@ -5,12 +5,12 @@ - - - GROUP_EVERYONE - Read - - + + + GROUP_EVERYONE + Read + + @@ -27,7 +27,7 @@ - + guest Read @@ -42,6 +42,7 @@ + diff --git a/config/alfresco/content-services-context.xml b/config/alfresco/content-services-context.xml index e54fc8d729..09201b08cd 100644 --- a/config/alfresco/content-services-context.xml +++ b/config/alfresco/content-services-context.xml @@ -72,6 +72,9 @@ + + + @@ -96,11 +99,7 @@ - - - - - + - - - - - + ${db.pool.max} + + ${db.pool.maxIdleTime} + 1 @@ -628,6 +631,13 @@ + + + + + + + diff --git a/config/alfresco/desktop/Alfresco.exe b/config/alfresco/desktop/Alfresco.exe index 1afb081373..94aee691ab 100644 Binary files a/config/alfresco/desktop/Alfresco.exe and b/config/alfresco/desktop/Alfresco.exe differ diff --git a/config/alfresco/extension/file-servers-custom.xml.sample b/config/alfresco/extension/file-servers-custom.xml.sample index a02dc1ff9c..570b4b570a 100644 --- a/config/alfresco/extension/file-servers-custom.xml.sample +++ b/config/alfresco/extension/file-servers-custom.xml.sample @@ -1,26 +1,32 @@ - - - - - - workspace://SpacesStore - /app:company_home - - - - - - _Alfresco.url - - - http://localhost:8080/alfresco/ - - - - + + + + + + + + + + workspace://SpacesStore + /app:company_home + + + + + + _Alfresco.url + + + http://localhost:8080/alfresco/ + + + + + + diff --git a/config/alfresco/extension/old-indexer-context.xml.sample b/config/alfresco/extension/old-indexer-context.xml.sample index 42c5904bc1..9d5a962fb1 100644 --- a/config/alfresco/extension/old-indexer-context.xml.sample +++ b/config/alfresco/extension/old-indexer-context.xml.sample @@ -85,4 +85,17 @@ + + + + org.alfresco.repo.search.impl.lucene.LuceneIndexerAndSearcherFactory$LuceneIndexBackupJob + + + + + + + + + \ No newline at end of file diff --git a/config/alfresco/file-servers.xml b/config/alfresco/file-servers.xml index 03dd91bdb6..7b9039b294 100644 --- a/config/alfresco/file-servers.xml +++ b/config/alfresco/file-servers.xml @@ -44,13 +44,40 @@ - - + + + + + + + + + + + + + org/jbpm/graph/action/Script.hbm.xml + org/jbpm/db/hibernate.queries.hbm.xml + org/jbpm/graph/def/ProcessDefinition.hbm.xml + org/jbpm/graph/def/Node.hbm.xml + org/jbpm/graph/def/Transition.hbm.xml + org/jbpm/graph/def/Event.hbm.xml + org/jbpm/graph/def/Action.hbm.xml + org/jbpm/graph/def/SuperState.hbm.xml + org/jbpm/graph/def/ExceptionHandler.hbm.xml + org/jbpm/instantiation/Delegation.hbm.xml + org/jbpm/graph/node/StartState.hbm.xml + org/jbpm/graph/node/EndState.hbm.xml + org/jbpm/graph/node/ProcessState.hbm.xml + org/jbpm/graph/node/Decision.hbm.xml + org/jbpm/graph/node/Fork.hbm.xml + org/jbpm/graph/node/Join.hbm.xml + org/jbpm/graph/node/State.hbm.xml + org/jbpm/graph/node/TaskNode.hbm.xml + org/jbpm/context/def/ContextDefinition.hbm.xml + org/jbpm/context/def/VariableAccess.hbm.xml + org/jbpm/taskmgmt/def/TaskMgmtDefinition.hbm.xml + org/jbpm/taskmgmt/def/Swimlane.hbm.xml + org/jbpm/taskmgmt/def/Task.hbm.xml + org/jbpm/taskmgmt/def/TaskController.hbm.xml + org/jbpm/module/def/ModuleDefinition.hbm.xml + org/jbpm/bytes/ByteArray.hbm.xml + org/jbpm/file/def/FileDefinition.hbm.xml + org/jbpm/scheduler/def/CreateTimerAction.hbm.xml + org/jbpm/scheduler/def/CancelTimerAction.hbm.xml + org/jbpm/graph/exe/Comment.hbm.xml + org/jbpm/graph/exe/ProcessInstance.hbm.xml + org/jbpm/graph/exe/Token.hbm.xml + org/jbpm/graph/exe/RuntimeAction.hbm.xml + org/jbpm/module/exe/ModuleInstance.hbm.xml + org/jbpm/context/exe/ContextInstance.hbm.xml + org/jbpm/context/exe/TokenVariableMap.hbm.xml + org/jbpm/context/exe/VariableInstance.hbm.xml + org/jbpm/context/exe/variableinstance/ByteArrayInstance.hbm.xml + org/jbpm/context/exe/variableinstance/DateInstance.hbm.xml + org/jbpm/context/exe/variableinstance/DoubleInstance.hbm.xml + org/jbpm/context/exe/variableinstance/HibernateLongInstance.hbm.xml + org/jbpm/context/exe/variableinstance/HibernateStringInstance.hbm.xml + org/jbpm/context/exe/variableinstance/LongInstance.hbm.xml + org/jbpm/context/exe/variableinstance/NullInstance.hbm.xml + org/jbpm/context/exe/variableinstance/StringInstance.hbm.xml + org/jbpm/msg/Message.hbm.xml + org/jbpm/msg/db/TextMessage.hbm.xml + org/jbpm/command/ExecuteActionCommand.hbm.xml + org/jbpm/command/ExecuteNodeCommand.hbm.xml + org/jbpm/command/SignalCommand.hbm.xml + org/jbpm/command/TaskInstanceEndCommand.hbm.xml + org/jbpm/taskmgmt/exe/TaskMgmtInstance.hbm.xml + org/jbpm/taskmgmt/exe/TaskInstance.hbm.xml + org/jbpm/taskmgmt/exe/PooledActor.hbm.xml + org/jbpm/taskmgmt/exe/SwimlaneInstance.hbm.xml + org/jbpm/scheduler/exe/Timer.hbm.xml + org/jbpm/logging/log/ProcessLog.hbm.xml + org/jbpm/logging/log/MessageLog.hbm.xml + org/jbpm/logging/log/CompositeLog.hbm.xml + org/jbpm/graph/log/ActionLog.hbm.xml + org/jbpm/graph/log/NodeLog.hbm.xml + org/jbpm/graph/log/ProcessInstanceCreateLog.hbm.xml + org/jbpm/graph/log/ProcessInstanceEndLog.hbm.xml + org/jbpm/graph/log/SignalLog.hbm.xml + org/jbpm/graph/log/TokenCreateLog.hbm.xml + org/jbpm/graph/log/TokenEndLog.hbm.xml + org/jbpm/graph/log/TransitionLog.hbm.xml + org/jbpm/context/log/VariableLog.hbm.xml + org/jbpm/context/log/VariableCreateLog.hbm.xml + org/jbpm/context/log/VariableDeleteLog.hbm.xml + org/jbpm/context/log/VariableUpdateLog.hbm.xml + org/jbpm/context/log/variableinstance/ByteArrayUpdateLog.hbm.xml + org/jbpm/context/log/variableinstance/DateUpdateLog.hbm.xml + org/jbpm/context/log/variableinstance/DoubleUpdateLog.hbm.xml + org/jbpm/context/log/variableinstance/HibernateLongUpdateLog.hbm.xml + org/jbpm/context/log/variableinstance/HibernateStringUpdateLog.hbm.xml + org/jbpm/context/log/variableinstance/LongUpdateLog.hbm.xml + org/jbpm/context/log/variableinstance/StringUpdateLog.hbm.xml + org/jbpm/taskmgmt/log/TaskLog.hbm.xml + org/jbpm/taskmgmt/log/TaskCreateLog.hbm.xml + org/jbpm/taskmgmt/log/TaskAssignLog.hbm.xml + org/jbpm/taskmgmt/log/TaskEndLog.hbm.xml + org/jbpm/taskmgmt/log/SwimlaneLog.hbm.xml + org/jbpm/taskmgmt/log/SwimlaneCreateLog.hbm.xml + org/jbpm/taskmgmt/log/SwimlaneAssignLog.hbm.xml + + + org/alfresco/repo/workflow/jbpm/WorkflowTaskInstance.hbm.xml @@ -66,9 +161,6 @@ ${cache.strategy} ${cache.strategy} ${cache.strategy} - ${cache.strategy} - ${cache.strategy} - ${cache.strategy} ${cache.strategy} ${cache.strategy} @@ -115,7 +207,7 @@ - + diff --git a/config/alfresco/import-export-context.xml b/config/alfresco/import-export-context.xml index 80e746bd7e..6d6d0c9d3f 100644 --- a/config/alfresco/import-export-context.xml +++ b/config/alfresco/import-export-context.xml @@ -218,6 +218,7 @@ ${version.major} ${version.minor} ${version.revision} + ${version.build} ${version.label} ${version.schema} ${version.edition} @@ -254,10 +255,12 @@ ${spaces.guest_home.childname} ${system.system_container.childname} ${system.people_container.childname} + ${system.workflow_container.childname} ${spaces.dictionary.childname} ${spaces.templates.childname} ${spaces.templates.content.childname} ${spaces.templates.email.childname} + ${spaces.templates.rss.childname} ${spaces.savedsearches.childname} ${spaces.scripts.childname} diff --git a/config/alfresco/messages/action-config.properties b/config/alfresco/messages/action-config.properties index 90302d5319..536d7e0608 100644 --- a/config/alfresco/messages/action-config.properties +++ b/config/alfresco/messages/action-config.properties @@ -78,3 +78,6 @@ script.description=Execute a JavaScript file to perform tasks such as creating n execute-all-rules.title=Execute all rules execute-all-rules.description=Execute all rules on the child items + +start-workflow.title=Start Workflow +start-workflow.description=This will start a workflow for the matched items. diff --git a/config/alfresco/messages/bootstrap-spaces.properties b/config/alfresco/messages/bootstrap-spaces.properties index c0d7937436..660be1d85e 100644 --- a/config/alfresco/messages/bootstrap-spaces.properties +++ b/config/alfresco/messages/bootstrap-spaces.properties @@ -7,7 +7,7 @@ spaces.dictionary.name=Data Dictionary spaces.dictionary.description=User managed definitions spaces.templates.name=Space Templates -spaces.templates.description=Space templates +spaces.templates.description=Space folder templates spaces.templates.content.name=Presentation Templates spaces.templates.content.description=Presentation templates @@ -15,6 +15,9 @@ spaces.templates.content.description=Presentation templates spaces.templates.email.name=Email Templates spaces.templates.email.description=Email templates +spaces.templates.rss.name=RSS Templates +spaces.templates.rss.description=RSS templates + spaces.savedsearches.name=Saved Searches spaces.savedsearches.description=Saved Searches diff --git a/config/alfresco/messages/bpm-messages.properties b/config/alfresco/messages/bpm-messages.properties new file mode 100644 index 0000000000..8a8886cb2d --- /dev/null +++ b/config/alfresco/messages/bpm-messages.properties @@ -0,0 +1,50 @@ +# Display labels for base Business Process Model + +bpm_businessprocessmodel.title=Business Process Model +bpm_businessprocessmodel.description=Base definitions of all Business Processes + +# Default transition +bpm_businessprocessmodel.transition.title=Done +bpm_businessprocessmodel.transition.description=Done + +# Base Task +bpm_businessprocessmodel.type.bpm_task.title=Task +bpm_businessprocessmodel.type.bpm_task.description=Task +bpm_businessprocessmodel.property.bpm_taskId.title=Task Identifier +bpm_businessprocessmodel.property.bpm_taskId.description=Task Identifier +bpm_businessprocessmodel.property.bpm_startDate.title=Task Start Date +bpm_businessprocessmodel.property.bpm_startDate.description=Task Start Date +bpm_businessprocessmodel.property.bpm_completionDate.title=Task Completion Date +bpm_businessprocessmodel.property.bpm_completionDate.description=Task Completion Date +bpm_businessprocessmodel.property.bpm_dueDate.title=Task Due Date +bpm_businessprocessmodel.property.bpm_dueDate.description=Task Due Date +bpm_businessprocessmodel.property.bpm_status.title=Status +bpm_businessprocessmodel.property.bpm_status.description=Status +bpm_businessprocessmodel.property.bpm_priority.title=Priority +bpm_businessprocessmodel.property.bpm_priority.description=Priority +bpm_businessprocessmodel.property.bpm_percentComplete.title=Percent Complete +bpm_businessprocessmodel.property.bpm_percentComplete.description=Percent Complete +bpm_businessprocessmodel.association.bpm_pooledActors.title=Pooled Users +bpm_businessprocessmodel.association.bpm_pooledActors.title=The users who may take ownership of the task + +# Workflow Task +bpm_businessprocessmodel.type.bpm_workflowTask.title=Worflow Task +bpm_businessprocessmodel.type.bpm_workflowTask.description=Task assigned by a Workflow +bpm_businessprocessmodel.property.bpm_workflowDefinitionId.title=Workflow Definition Id +bpm_businessprocessmodel.property.bpm_workflowDefinitionId.description=Workflow Definition Id +bpm_businessprocessmodel.property.bpm_workflowInstanceId.title=Workflow Instance Id +bpm_businessprocessmodel.property.bpm_workflowInstanceId.description=Workflow Instance Id +bpm_businessprocessmodel.property.bpm_context.title=Task Context +bpm_businessprocessmodel.property.bpm_context.description=The context within which this task has been assigned +bpm_businessprocessmodel.property.bpm_outcome.title=Task Outcome +bpm_businessprocessmodel.property.bpm_outcome.description=Decision made on completing Task +bpm_businessprocessmodel.property.bpm_completedItems.title=Completed Items +bpm_businessprocessmodel.property.bpm_completedItems.description=Package items marked as complete +bpm_businessprocessmodel.property.bpm_packageActionGroup.title=Package Actions +bpm_businessprocessmodel.property.bpm_packageActionGroup.description=Actions available on workflow package +bpm_businessprocessmodel.property.bpm_packageItemActionGroup.title=Package Item Actions +bpm_businessprocessmodel.property.bpm_packageItemActionGroup.description=Actions available on workflow package items +bpm_businessprocessmodel.association.bpm_package.title=Content Package +bpm_businessprocessmodel.association.bpm_package.description=The collection of content routed through the workflow +bpm_businessprocessmodel.aspect.bpm_workflowPackage.title=Workflow Package +bpm_businessprocessmodel.aspect.bpm_workflowPackage.description=The collection of content routed through the workflow diff --git a/config/alfresco/messages/patch-service.properties b/config/alfresco/messages/patch-service.properties index 17eac74a1a..d98071a699 100644 --- a/config/alfresco/messages/patch-service.properties +++ b/config/alfresco/messages/patch-service.properties @@ -72,3 +72,27 @@ patch.topLevelGroupParentChildAssociationTypePatch.description=Ensure top level patch.topLevelGroupParentChildAssociationTypePatch.result=Fixed {0} top level groups child association types. patch.topLevelGroupParentChildAssociationTypePatch.err.sys_path_not_found=Required authority system path not found: {0} patch.topLevelGroupParentChildAssociationTypePatch.err.auth_path_not_found=Required authority path not found: {0} + +patch.actionRuleDecouplingPatch.description=Migrate existing rules to the updated model where rules are decoupled from actions. +patch.actionRuleDecouplingPatch.result=Updated {0} rules. + +patch.systemWorkflowFolder.description=Ensures the existence of the system workflow container. +patch.systemWorkflowFolder.result.created=Created system workflow container {0}. + +patch.rssTemplatesFolder.description=Ensures the existence of the 'RSS Templates' folder. +patch.rssTemplatesFolder.result.exists=The RSS Templates folder already exists: {0} +patch.rssTemplatesFolder.result.created=The RSS Templates folder was successfully created: {0} + +patch.uifacetsAspectRemovalPatch.description=Removes the incorrectly applied uifacets aspect from presentation template files. +patch.uifacetsAspectRemovalPatch.updated=Successfully removed the uifacets aspect from {0} presentation template files. + +patch.guestPersonPermission2.description=Change Guest Person permission to visible by all users as 'Consumer'. +patch.guestPersonPermission2.result=Updated Guest Person permission to visible by all users as 'Consumer'. + +patch.schemaUpgradeScript.description=Ensures that the database upgrade script has been run. +patch.schemaUpgradeScript.err.not_executed=The schema upgrade script, ''{0}'', has not been run against this database. + +patch.uniqueChildName.description=Checks and renames duplicate children. +patch.uniqueChildName.copyOf=({0}) +patch.uniqueChildName.result=Checked {0} associations and fixed {1} duplicates. See file {2} for details. + diff --git a/config/alfresco/messages/permissions-service.properties b/config/alfresco/messages/permissions-service.properties index 368e3f7c82..3613df7366 100644 --- a/config/alfresco/messages/permissions-service.properties +++ b/config/alfresco/messages/permissions-service.properties @@ -1 +1,2 @@ -permissions.err_access_denied=Access Denied. You do not have the appropriate permissions to perform this operation. +permissions.err_access_denied=Access Denied. You do not have the appropriate permissions to perform this operation. +permissions.err_read_only=Access Denied. The system is currently in read-only mode. \ No newline at end of file diff --git a/config/alfresco/messages/system-messages.properties b/config/alfresco/messages/system-messages.properties index 6dc3a63764..99ce516daf 100644 --- a/config/alfresco/messages/system-messages.properties +++ b/config/alfresco/messages/system-messages.properties @@ -1,3 +1,4 @@ # System-related messages system.err.property_not_set=Property ''{0}'' has not been set: {1} +system.err.duplicate_name=Duplicate child name not allowed: {0} \ No newline at end of file diff --git a/config/alfresco/messages/workflow-messages.properties b/config/alfresco/messages/workflow-messages.properties new file mode 100644 index 0000000000..c6053ad28e --- /dev/null +++ b/config/alfresco/messages/workflow-messages.properties @@ -0,0 +1,68 @@ +# Display labels for out-of-the-box Content-oriented Workflows + +# +# Review & Approve Workflow +# + +wf_review.workflow.title=Review & Approve +wf_review.workflow.description=Review & approval of content + +# Review & Approve Task Definitions + +wf_workflowmodel.type.wf_submitReviewTask.title=Submit Review +wf_workflowmodel.type.wf_submitReviewTask.description=Submit documents for review & approval +wf_workflowmodel.property.wf_reviewDueDate.title=Review Due Date +wf_workflowmodel.property.wf_reviewDueDate.description=Review Due Date +wf_workflowmodel.property.wf_reviewPriority.title=Review Priority +wf_workflowmodel.property.wf_reviewPriority.description=Review Priority +wf_workflowmodel.association.wf_reviewer.title=Reviewer +wf_workflowmodel.association.wf_reviewer.description=Reviewer + +wf_workflowmodel.type.wf_reviewTask.title=Review +wf_workflowmodel.type.wf_reviewTask.description=Review Documents to Approve or Reject them + +# Review & Approve Process Definitions + +wf_review.node.start.title=Start +wf_review.node.start.description=Start +wf_review.node.review.title=Review +wf_review.node.review.description=Review +wf_review.node.review.transition.reject.title=Reject +wf_review.node.review.transition.reject.description=Reject +wf_review.node.review.transition.approve.title=Approve +wf_review.node.review.transition.approve.description=Approve +wf_review.node.rejected.title=Rejected +wf_review.node.rejected.description=Rejected +wf_review.task.wf_rejectedTask.title=Rejected +wf_review.task.wf_rejectedTask.description=Rejected +wf_review.node.approved.title=Approved +wf_review.node.approved.description=Approved +wf_review.task.wf_approvedTask.title=Approved +wf_review.task.wf_approvedTask.description=Approved +wf_review.node.end.title=End +wf_review.node.end.description=End + + +# +# Adhoc Task Workflow +# + +wf_adhoc.workflow.title=Adhoc Task +wf_adhoc.workflow.description=Assign task to colleague + +# Adhoc Task Definitions + +wf_workflowmodel.type.wf_submitAdhocTask.title=Submit Adhoc Task +wf_workflowmodel.type.wf_submitAdhocTask.description=Allocate task to colleague +wf_workflowmodel.property.wf_adhocDescription.title=Task Description +wf_workflowmodel.property.wf_adhocDescription.description=Description of what needs to be achieved +wf_workflowmodel.property.wf_adhocDueDate.description=Task Due Date +wf_workflowmodel.property.wf_adhocPriority.title=Task Priority +wf_workflowmodel.property.wf_notifyMe.title=Notify Me +wf_workflowmodel.property.wf_notifyMe.description=Notify me when task is complete +wf_workflowmodel.association.wf_assignee.title=Assignee +wf_workflowmodel.association.wf_assignee.description=Who's doing the task +wf_workflowmodel.type.wf_adhocTask.title=Adhoc Task +wf_workflowmodel.type.wf_adhocTask.description=Adhoc Task allocated by colleague +wf_workflowmodel.type.wf_completedAdhocTask.title=Adhoc Task Completed +wf_workflowmodel.type.wf_completedAdhocTask.description=Adhoc Task Completed diff --git a/config/alfresco/mimetype/mimetype-map.xml b/config/alfresco/mimetype/mimetype-map.xml index 17340ab60e..da308c86f2 100644 --- a/config/alfresco/mimetype/mimetype-map.xml +++ b/config/alfresco/mimetype/mimetype-map.xml @@ -138,6 +138,9 @@ jpeg jpe + + svg + js diff --git a/config/alfresco/mimetype/openoffice-document-formats.xml b/config/alfresco/mimetype/openoffice-document-formats.xml index 62a0040d75..1c18b17845 100644 --- a/config/alfresco/mimetype/openoffice-document-formats.xml +++ b/config/alfresco/mimetype/openoffice-document-formats.xml @@ -7,7 +7,7 @@ application/pdf pdf - Presentationimpress_pdf_Export + Presentationimpress_pdf_Export Spreadsheetcalc_pdf_Export Textwriter_pdf_Export @@ -30,7 +30,7 @@ text/html html - Presentationimpress_html_Export + Presentationimpress_html_Export SpreadsheetHTML (StarCalc) TextHTML (StarWriter) diff --git a/config/alfresco/model-specific-services-context.xml b/config/alfresco/model-specific-services-context.xml index bd6bc7fcbc..287b666e72 100644 --- a/config/alfresco/model-specific-services-context.xml +++ b/config/alfresco/model-specific-services-context.xml @@ -19,6 +19,7 @@ /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.templates.childname} /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.templates.content.childname} /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.templates.email.childname} + /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.templates.rss.childname} /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.scripts.childname} diff --git a/config/alfresco/model/applicationModel.xml b/config/alfresco/model/applicationModel.xml index f11cc2ef2b..078a7d17a1 100644 --- a/config/alfresco/model/applicationModel.xml +++ b/config/alfresco/model/applicationModel.xml @@ -123,6 +123,17 @@ + + + Feed Source + + + Feed Template + d:noderef + false + + + diff --git a/config/alfresco/model/bpmModel.xml b/config/alfresco/model/bpmModel.xml new file mode 100644 index 0000000000..45cd982c46 --- /dev/null +++ b/config/alfresco/model/bpmModel.xml @@ -0,0 +1,270 @@ + + + + + Business Process Model + Alfresco + 1.0 + + + + + + + + + + + + + + + + + + + + 1 + 2 + 3 + + + + + + + + + Not Yet Started + In Progress + On Hold + Cancelled + Completed + + + + + + 0 + 100 + + + + + + + + + + + + Task + Task + cm:content + + + + + + + + + + + + + Task Identifier + d:long + true + true + + + + + + + Start Date + d:date + true + + + End Date + d:date + true + + + Due Date + d:date + + + + + + + Status + d:text + true + Not Yet Started + + + + + + Priority + d:int + true + 2 + + + + + + Percentage Complete + d:int + true + 0 + + + + + + + + + + + + + + + + + Pooled Users + + false + false + + + cm:person + false + true + + + + + + + cm:ownable + + + + + + + + + + Workflow Task + Task assigned via Workflow + bpm:task + + + + + + + Task Context + d:noderef + + + + + Task Outcome + d:text + + + + + d:noderef + true + + + + + d:text + + + + + d:text + workflow_item_read_actions + + + + + + + Workflow Package + + false + false + + + bpm:workflowPackage + true + false + + + + + + + + + + + + + + + + + + + + Workflow Package + + + + + + + + Workflow Definition Id + d:text + + + Workflow Definition Name + d:text + + + Workflow Instance Id + d:text + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/alfresco/model/contentModel.xml b/config/alfresco/model/contentModel.xml index f45e1b43d7..0f38776be5 100644 --- a/config/alfresco/model/contentModel.xml +++ b/config/alfresco/model/contentModel.xml @@ -45,11 +45,6 @@ Folder cm:cmobject true - - - d:boolean - - @@ -81,10 +76,6 @@ true - - d:boolean - false - @@ -186,8 +177,14 @@ cm:cmobject - + + false + true + + cm:category + false + true @@ -201,8 +198,14 @@ cm:cmobject + + false + true + cm:category + false + true @@ -669,6 +672,10 @@ d:text true + + Subject + d:text + Sent Date d:datetime diff --git a/config/alfresco/model/systemModel.xml b/config/alfresco/model/systemModel.xml index 4a50d06709..be70ff601e 100644 --- a/config/alfresco/model/systemModel.xml +++ b/config/alfresco/model/systemModel.xml @@ -41,6 +41,9 @@ d:text + + d:text + d:int true @@ -127,6 +130,11 @@ Incomplete + + + Temporary + + Archived diff --git a/config/alfresco/model/workflowModel.xml b/config/alfresco/model/workflowModel.xml new file mode 100644 index 0000000000..0185344dee --- /dev/null +++ b/config/alfresco/model/workflowModel.xml @@ -0,0 +1,160 @@ + + + + + + + + + + + + + + + + + + + + + + Submit Review Task + bpm:workflowTask + + + + + Review Due Date + d:date + + + + Review Priority + d:int + 2 + + + + + + + + + + + Reviewer + + false + false + + + cm:person + true + false + + + + + + + + + workflow_collection_actions + + + + workflow_item_collection_actions + + + + + + + + + Review Task + bpm:workflowTask + + + + + workflow_item_edit_actions + + + + + + + + + + + + + + + bpm:workflowTask + + + + Description + d:text + + + + + + Submit Adhoc Task + wf:baseAdhocTask + + + + Due Date + d:date + + + + Priority + d:int + 2 + + + + + + + Email Notification + d:boolean + + + + + + Assignee + + false + false + + + cm:person + true + false + + + + + + + Adhoc Task + wf:baseAdhocTask + + + + Completed Adhoc Task + wf:baseAdhocTask + + + + + \ No newline at end of file diff --git a/config/alfresco/network-protocol-context.xml b/config/alfresco/network-protocol-context.xml index 170cd26ba1..6e615800a0 100644 --- a/config/alfresco/network-protocol-context.xml +++ b/config/alfresco/network-protocol-context.xml @@ -56,8 +56,8 @@ - + diff --git a/config/alfresco/patch/patch-services-context.xml b/config/alfresco/patch/patch-services-context.xml index 2cca6f90f9..0241ae4919 100644 --- a/config/alfresco/patch/patch-services-context.xml +++ b/config/alfresco/patch/patch-services-context.xml @@ -37,6 +37,9 @@ + + + @@ -340,7 +343,6 @@ - patch.scriptsFolder patch.scriptsFolder.description @@ -359,8 +361,7 @@ - - + patch.topLevelGroupParentChildAssociationTypePatch patch.topLevelGroupParentChildAssociationTypePatch.description @@ -369,4 +370,107 @@ 14 + + patch.actionRuleDecouplingPatch + patch.actionRuleDecouplingPatch.description + 0 + 14 + 15 + + + + patch.systemWorkflowFolderPatch + patch.systemWorkflowFolder.description + 0 + 15 + 16 + + + + + patch.rssFolder + patch.rssTemplatesFolder.description + 0 + 16 + 17 + alfresco/templates/rss_templates.acp + + + + + + + + + + + + + + + + + patch.uifacetsTemplates + patch.uifacetsAspectRemovalPatch.description + 0 + 17 + 18 + + + + + + + + + + + patch.guestPersonPermission2 + patch.guestPersonPermission2.description + 0 + 18 + 19 + + + + + + + + + + + patch.schemaUpdateScript-V1.4-1 + patch.patch.schemaUpgradeScriptPatch.description + 0 + 19 + 20 + + AlfrescoSchemaUpdate-1.4-1-xxx.sql + + + + patch.uniqueChildName + patch.uniqueChildName.description + 0 + 19 + 20 + + + + + + + + + + + + + + + + + + diff --git a/config/alfresco/public-services-context.xml b/config/alfresco/public-services-context.xml index aeb6ab1d24..66b2aca4a2 100644 --- a/config/alfresco/public-services-context.xml +++ b/config/alfresco/public-services-context.xml @@ -934,6 +934,42 @@ + + + + + + + org.alfresco.service.cmr.workflow.WorkflowService + + + + + + + + + + + + + + + + + ${server.transaction.mode.default} + + + + + + + org.alfresco.service.cmr.workflow.WorkflowService + + + Workflow Service + + diff --git a/config/alfresco/public-services-security-context.xml b/config/alfresco/public-services-security-context.xml index 0081b9874c..bbe50d57eb 100644 --- a/config/alfresco/public-services-security-context.xml +++ b/config/alfresco/public-services-security-context.xml @@ -64,6 +64,9 @@ + + + @@ -95,6 +98,9 @@ + + + @@ -367,6 +373,7 @@ org.alfresco.service.cmr.model.FileFolderService.listFiles=ACL_NODE.0.sys:base.ReadChildren,AFTER_ACL_NODE.sys:base.Read org.alfresco.service.cmr.model.FileFolderService.listFolders=ACL_NODE.0.sys:base.ReadChildren,AFTER_ACL_NODE.sys:base.Read org.alfresco.service.cmr.model.FileFolderService.search=ACL_NODE.0.sys:base.ReadChildren,AFTER_ACL_NODE.sys:base.Read + org.alfresco.service.cmr.model.FileFolderService.searchSimple=ACL_NODE.0.sys:base.ReadChildren,AFTER_ACL_NODE.sys:base.Read org.alfresco.service.cmr.model.FileFolderService.rename=ACL_PARENT.0.sys:base.CreateChildren,AFTER_ACL_NODE.sys:base.WriteProperties org.alfresco.service.cmr.model.FileFolderService.move=ACL_NODE.0.sys:base.DeleteNode,ACL_NODE.1.sys:base.CreateChildren org.alfresco.service.cmr.model.FileFolderService.copy=ACL_NODE.0.sys:base.Read,ACL_NODE.1.sys:base.CreateChildren @@ -399,6 +406,7 @@ org.alfresco.service.cmr.repository.ContentService.getWriter=ACL_NODE.0.sys:base.WriteContent org.alfresco.service.cmr.repository.ContentService.isTransformable=ACL_ALLOW org.alfresco.service.cmr.repository.ContentService.getTransformer=ACL_ALLOW + org.alfresco.service.cmr.repository.ContentService.getImageTransformer=ACL_ALLOW org.alfresco.service.cmr.repository.ContentService.transform=ACL_ALLOW org.alfresco.service.cmr.repository.ContentService.getTempWriter=ACL_ALLOW @@ -684,7 +692,6 @@ org.alfresco.service.cmr.security.PersonService.deletePerson=ACL_METHOD.ROLE_ADMINISTRATOR org.alfresco.service.cmr.security.PersonService.getAllPeople=ACL_ALLOW org.alfresco.service.cmr.security.PersonService.getPeopleContainer=ACL_ALLOW - org.alfresco.service.cmr.security.PersonService.getUserNamesAreCaseSensitive=ACL_ALLOW diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties index b912aa75b6..82cdc1ceef 100644 --- a/config/alfresco/repository.properties +++ b/config/alfresco/repository.properties @@ -61,6 +61,7 @@ db.username=alfresco db.password=alfresco db.pool.initial=10 db.pool.max=20 +db.pool.maxIdleTime=120 # Email configuration @@ -96,6 +97,7 @@ spaces.dictionary.childname=app:dictionary spaces.templates.childname=app:space_templates spaces.templates.content.childname=app:content_templates spaces.templates.email.childname=app:email_templates +spaces.templates.rss.childname=app:rss_templates spaces.savedsearches.childname=app:saved_searches spaces.scripts.childname=app:scripts @@ -104,7 +106,21 @@ spaces.scripts.childname=app:scripts system.system_container.childname=sys:system system.people_container.childname=sys:people +# Folders for storing workflow related info + +system.workflow_container.childname=sys:workflow + # Are user names case sensitive? +# ============================== +# +# NOTE: If you are using mysql you must have case sensitive collation +# +# You can do this when creating the alfresco database at the start +# CREATE DATABASE alfresco CHARACTER SET utf8 COLLATION utf8_bin; +# If you want to do this later this is a dump and load fix as it is done when the database, tables and columns are created. +# +# Must other databases are case sensitive by default. +# user.name.caseSensitive=false diff --git a/config/alfresco/rule-services-context.xml b/config/alfresco/rule-services-context.xml index 1ddff2a2fa..79955d57a9 100644 --- a/config/alfresco/rule-services-context.xml +++ b/config/alfresco/rule-services-context.xml @@ -18,12 +18,12 @@ - - - + + false + @@ -55,10 +55,7 @@ - - - - + @@ -84,6 +81,7 @@ + @@ -126,15 +124,6 @@ - - - onDeleteNode - - - true - - - onCreateChildAssociation @@ -143,7 +132,19 @@ - onDeleteChildAssociation + beforeDeleteChildAssociation + + + true + + + + + + beforeDeleteNode + + + true diff --git a/config/alfresco/scheduled-jobs-context.xml b/config/alfresco/scheduled-jobs-context.xml index acae999cd2..f0acdafbbb 100644 --- a/config/alfresco/scheduled-jobs-context.xml +++ b/config/alfresco/scheduled-jobs-context.xml @@ -113,7 +113,7 @@ - org.alfresco.repo.search.impl.lucene.LuceneIndexerAndSearcherFactory$LuceneIndexBackupJob + org.alfresco.repo.search.impl.lucene.LuceneIndexerAndSearcherFactory2$LuceneIndexBackupJob diff --git a/config/alfresco/script-services-context.xml b/config/alfresco/script-services-context.xml index 464564d7de..fbbd6777a0 100644 --- a/config/alfresco/script-services-context.xml +++ b/config/alfresco/script-services-context.xml @@ -3,11 +3,8 @@ - - - - - + + diff --git a/config/alfresco/templates/content_template_examples.xml b/config/alfresco/templates/content_template_examples.xml index 078bf14ffb..f993d5a5fe 100644 --- a/config/alfresco/templates/content_template_examples.xml +++ b/config/alfresco/templates/content_template_examples.xml @@ -2,210 +2,155 @@ - - + true - admin - 2006-02-10T09:10:45.906Z Displays basic information about the current document - admin contentUrl=classpath:alfresco/templates/content/examples/doc_info.ftl|mimetype=text/plain|size=636|encoding=UTF-8 doc_info.ftl doc_info.ftl - 2005-10-21T15:11:00.446+01:00 - - + true - admin - 2006-02-10T09:10:50.390Z Calculates if the document has the localizable aspect applied - admin contentUrl=classpath:alfresco/templates/content/examples/localizable.ftl|mimetype=text/plain|size=380|encoding=UTF-8 localizable.ftl localizable.ftl - 2005-10-21T15:11:02.181+01:00 - - + true - admin - 2006-02-10T09:10:51.937Z Displays a list of the documents in the current user Home Space - admin contentUrl=classpath:alfresco/templates/content/examples/my_docs.ftl|mimetype=text/plain|size=750|encoding=UTF-8 my_docs.ftl my_docs.ftl - 2005-10-21T15:11:03.118+01:00 - - + true - admin - 2006-02-10T09:10:57.890Z Displays a list of spaces in the current user Home Space - admin contentUrl=classpath:alfresco/templates/content/examples/my_spaces.ftl|mimetype=text/plain|size=682|encoding=UTF-8 my_spaces.ftl my_spaces.ftl - 2005-10-21T15:11:04.665+01:00 - - + true - admin - 2006-02-10T09:10:59.421Z Shows a simple summary page about the current user and their Home Space - admin contentUrl=classpath:alfresco/templates/content/examples/my_summary.ftl|mimetype=text/plain|size=537|encoding=UTF-8 my_summary.ftl my_summary.ftl - 2005-10-21T15:11:05.509+01:00 - - + true - admin - 2006-02-10T09:11:04.953Z Calculates if the document has the translatable aspect applied - admin contentUrl=classpath:alfresco/templates/content/examples/translatable.ftl|mimetype=text/plain|size=415|encoding=UTF-8 translatable.ftl translatable.ftl - 2005-10-21T15:11:06.306+01:00 - - + true - admin - 2006-02-10T09:10:41.656Z Renders a valid RSS2.0 XML document showing the documents in the current space created or modified in the last 7 days. The template should be configured to use the appropriate server and port before use. - admin contentUrl=classpath:alfresco/templates/content/examples/RSS_2.0_recent_docs.ftl|mimetype=text/plain|size=1917|encoding=UTF-8 RSS_2.0_recent_docs.ftl RSS_2.0_recent_docs.ftl - 2006-01-13T15:49:50.695Z - - + true - admin - 2006-02-10T09:11:00.953Z Displays a list of the documents in the current space created or modified in the last 7 days - admin contentUrl=classpath:alfresco/templates/content/examples/recent_docs.ftl|mimetype=text/plain|size=968|encoding=UTF-8 recent_docs.ftl recent_docs.ftl - 2006-01-13T15:50:02.164Z - - + true - admin - 2006-02-10T09:10:48.062Z Example of various lists of documents, spaces and summary information about the current user - admin contentUrl=classpath:alfresco/templates/content/examples/general_example.ftl|mimetype=text/plain|size=1608|encoding=UTF-8 general_example.ftl general_example.ftl - 2006-02-10T09:10:47.796Z - - + true - admin - 2006-02-10T09:10:53.531Z Displays a list of the documents in the current user Home Space. Text document content is shown inline, as is JPG content as small thumbnail images. - admin contentUrl=classpath:alfresco/templates/content/examples/my_docs_inline.ftl|mimetype=text/plain|size=652|encoding=UTF-8 my_docs_inline.ftl my_docs_inline.ftl - 2006-02-10T09:10:53.281Z - - + true - admin - 2006-02-10T09:11:07.031Z Example of using XPath and Lucene searches within a template. - admin contentUrl=classpath:alfresco/templates/content/examples/xpath_search.ftl|mimetype=text/plain|size=1109|encoding=UTF-8 xpath_search.ftl xpath_search.ftl - 2006-02-10T09:11:06.750Z diff --git a/config/alfresco/templates/rss_templates.acp b/config/alfresco/templates/rss_templates.acp new file mode 100644 index 0000000000..6ca240207f Binary files /dev/null and b/config/alfresco/templates/rss_templates.acp differ diff --git a/config/alfresco/version.properties b/config/alfresco/version.properties index 4dd47fc44c..4daccb2687 100644 --- a/config/alfresco/version.properties +++ b/config/alfresco/version.properties @@ -7,12 +7,16 @@ version.major=1 version.minor=4 version.revision=0 -version.label=RC2 (dev) +version.label=Dev # Edition label version.edition=Community Network +# Build number + +version.build=@build-number@ + # Schema number -version.schema=14 +version.schema=20 diff --git a/config/alfresco/workflow-context.xml b/config/alfresco/workflow-context.xml index 98abbb5311..097521554a 100644 --- a/config/alfresco/workflow-context.xml +++ b/config/alfresco/workflow-context.xml @@ -3,17 +3,118 @@ - - - - + + + + + + + + + jbpm + org/alfresco/repo/workflow/jbpm/review_and_approve_processdefinition.xml + text/xml + false + + + jbpm + org/alfresco/repo/workflow/jbpm/adhoc_task_processdefinition.xml + text/xml + false + + + - - - + + + + alfresco/model/bpmModel.xml + alfresco/model/workflowModel.xml + + + + + alfresco/messages/bpm-messages + alfresco/messages/workflow-messages + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/source/cpp/CAlfrescoApp/CAlfrescoApp.aps b/source/cpp/CAlfrescoApp/CAlfrescoApp.aps index 8e5c213a85..34c423008b 100644 Binary files a/source/cpp/CAlfrescoApp/CAlfrescoApp.aps and b/source/cpp/CAlfrescoApp/CAlfrescoApp.aps differ diff --git a/source/cpp/CAlfrescoApp/CAlfrescoApp.cpp b/source/cpp/CAlfrescoApp/CAlfrescoApp.cpp index d20ed61c55..2120081d24 100644 --- a/source/cpp/CAlfrescoApp/CAlfrescoApp.cpp +++ b/source/cpp/CAlfrescoApp/CAlfrescoApp.cpp @@ -18,13 +18,13 @@ #include "stdafx.h" #include "CAlfrescoApp.h" #include "CAlfrescoAppDlg.h" -#include "FileStatusDialog.h" #include #include "util\String.h" #include "util\DataBuffer.h" #include "util\FileName.h" +#include "util\Integer.h" #include @@ -82,6 +82,7 @@ BOOL CCAlfrescoAppApp::InitInstance() // Get the path to the folder containing the application String folderPath = appPath.substring(0, pos); + String exeName = appPath.substring(pos + 1); // Create the Alfresco interface @@ -90,27 +91,69 @@ BOOL CCAlfrescoAppApp::InitInstance() try { - // If there are no file paths on the command line then display a status page for the files - // in the Alfresco folder + // Get the action information - if ( __argc == 1) { + AlfrescoActionInfo actionInfo = alfresco.getActionInformation(exeName); - // Display status for the files in the Alfresco folder + // Check if the action should be confirmed - doFolderStatus( alfresco); + if ( actionInfo.hasPreProcessAction(PreConfirmAction)) { + + // Get the confirmation message + + String confirmMsg = actionInfo.getConfirmationMessage(); + if ( confirmMsg.length() == 0) + confirmMsg = L"Run action ?"; + + // Display a confirmation dialog + + if ( AfxMessageBox( confirmMsg, MB_OKCANCEL | MB_ICONQUESTION) == IDCANCEL) + return FALSE; } - else { - // Build a list of the file names + // Check if the action supports multiple paths, if not then call the action once for each supplied path - StringList fileList; + if ( actionInfo.hasAttribute(AttrMultiplePaths)) { + + // Build a list of paths from the command line arguments + + StringList pathList; for ( int i = 1; i < __argc; i++) - fileList.addString( String(__wargv[i])); + pathList.addString( String(__wargv[i])); - // Process the file list and check in or out each file + // Run the action - doCheckInOut( alfresco, fileList); + runAction( alfresco, pathList, actionInfo); + } + + // Check if the action supports file/folder targets + + else if ( actionInfo.hasAttribute( AttrAnyFilesFolders) == true) { + + // Pass one path at a time to the action + + for ( int i = 1; i < __argc; i++) { + + // Create a path list with a single path + + StringList pathList; + pathList.addString( String(__wargv[i])); + + // Run the action + + runAction( alfresco, pathList, actionInfo); + } + } + + // Action does not use targets, just run the action + + else if ( actionInfo.allowsNoParameters()) { + + // Run the action + + StringList emptyList; + runAction( alfresco, emptyList, actionInfo); } } catch (Exception ex) { @@ -124,128 +167,36 @@ BOOL CCAlfrescoAppApp::InitInstance() return 1; } - // Run the main dialog -/** - CCAlfrescoAppDlg dlg; - m_pMainWnd = &dlg; - INT_PTR nResponse = dlg.DoModal(); - if (nResponse == IDOK) - { - // TODO: Place code here to handle when the dialog is - // dismissed with OK - } - else if (nResponse == IDCANCEL) - { - // TODO: Place code here to handle when the dialog is - // dismissed with Cancel - } -**/ + // Exit the application - // Since the dialog has been closed, return FALSE so that we exit the - // application, rather than start the application's message pump. return FALSE; } /** - * Display file status of the files in the target Alfresco folder - * - * @param AlfrescoInterface& alfresco - * @param const wchar_t* fileSpec - * @return bool - */ -bool CCAlfrescoAppApp::doFolderStatus( AlfrescoInterface& alfresco, const wchar_t* fileSpec) { - - // Get the base UNC path - - String uncPath = alfresco.getUNCPath(); - uncPath.append(PathSeperator); - - // Search the Alfresco folder - - WIN32_FIND_DATA findData; - String searchPath = uncPath; - searchPath.append( fileSpec); - - bool sts = false; - HANDLE fHandle = FindFirstFile( searchPath, &findData); - AlfrescoFileInfoList fileList; - - if ( fHandle != INVALID_HANDLE_VALUE) { - - // Loop until all files have been returned - - PTR_AlfrescoFileInfo pFileInfo; - sts = true; - - while ( fHandle != INVALID_HANDLE_VALUE) { - - // Get the file name, ignore the '.' and '..' files - - String fName = findData.cFileName; - - if ( fName.equals(L".") || fName.equals(L"..")) { - - // Get the next file/folder name in the search - - if ( FindNextFile( fHandle, &findData) == 0) - fHandle = INVALID_HANDLE_VALUE; - continue; - } - - // Get the file information for the current file folder - - pFileInfo = alfresco.getFileInformation( findData.cFileName); - - if ( pFileInfo.get() != NULL) { - - // Add the file to the list - - fileList.addInfo( pFileInfo); - } - - // Get the next file/folder name in the search - - if ( FindNextFile( fHandle, &findData) == 0) - fHandle = INVALID_HANDLE_VALUE; - } - } - - // Display the file status dialog if there are files to display - - if ( fileList.size() > 0) { - - // Display the file status dialog - - CFileStatusDialog dlg( fileList); - dlg.DoModal(); - } - else { - CString msg; - msg.FormatMessage( L"No files found in %1", uncPath.data()); - AfxMessageBox( msg, MB_OK | MB_ICONINFORMATION); - } - - // Return status - - return sts; -} - -/** - * Process the list of files and check in or out each file + * Process the command line arguments and build the parameter list for the desktop action * * @param alfresco AlfrescoInterface& - * @param files StringList& + * @param paths StringList& + * @param actionInfo AlfrescoActionInfo& + * @param params DesktopParams& + * @return bool */ -bool CCAlfrescoAppApp::doCheckInOut( AlfrescoInterface& alfresco, StringList& files) { +bool CCAlfrescoAppApp::buildDesktopParameters( AlfrescoInterface& alfresco, StringList& paths, AlfrescoActionInfo& actionInfo, + DesktopParams& params) { + + // If there are no paths then just return a success + + if ( paths.numberOfStrings() == 0) + return true; // Process the list of files and either check in the file if it is a working copy or check out // the file - for ( unsigned int i = 0; i < files.numberOfStrings(); i++) { + for ( unsigned int i = 0; i < paths.numberOfStrings(); i++) { // Get the current file name - String curFile = files.getStringAt( i); + String curFile = paths.getStringAt( i); // Check if the path is on an Alfresco mapped drive @@ -259,171 +210,181 @@ bool CCAlfrescoAppApp::doCheckInOut( AlfrescoInterface& alfresco, StringList& fi curFile = uncPath; } - // Check that the path is to a file + // Check if the path is to a file/folder, and whether it is a local path bool copyFile = false; - DWORD attr = GetFileAttributes( curFile); - if ( attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY) == 0) { + + if ( attr != INVALID_FILE_ATTRIBUTES) { + + // Check if the action supports the file/folder type + + bool isDir = (attr & FILE_ATTRIBUTE_DIRECTORY) != 0 ? true : false; + + if ( isDir && actionInfo.supportsFolders() == false) { + AfxMessageBox(L"Action does not support folders", MB_OK | MB_ICONSTOP); + return false; + } + else if ( actionInfo.supportsFiles() == false) { + AfxMessageBox(L"Action does not support files", MB_OK | MB_ICONSTOP); + return false; + } // Get the file name from the path StringList nameParts = FileName::splitPath( curFile); String curName = nameParts.getStringAt( 1); - // Get the Alfresco file status information - - PTR_AlfrescoFileInfo pFileInfo = alfresco.getFileInformation( curName); - // If the path is to a file that is not on the Alfresco share the file will need to be copied, // after checking the status of a matching file in the Alfresco folder if ( curFile.length() >= 3 && curFile.substring(1,3).equals( L":\\")) { - // Check if there is an existing file with the same name + // Check if the action supports local files - if ( pFileInfo.get() != NULL) { + if ( isDir == false && actionInfo.hasAttribute(AttrClientFiles) == false) { + AfxMessageBox(L"Action does not support local files", MB_OK | MB_ICONSTOP); + return false; + } + else if ( isDir == true && actionInfo.hasAttribute(AttrClientFolders) == false) { + AfxMessageBox(L"Action does not support local folders", MB_OK | MB_ICONSTOP); + return false; + } - // Check if the file is a working copy + // Check if there is an existing file in the Alfresco with the same name, check if the file is locked + + PTR_AlfrescoFileInfo fInfo = alfresco.getFileInformation( curName); + if ( fInfo.get() != NULL) { - if ( pFileInfo->isWorkingCopy()) { + // There is an existing file in the Alfresco folder with the same name, check if it is locked - // Local file matches a working copy file in the Alfresco folder - - CString msg; - msg.FormatMessage( L"Found matching working copy for local file %1", curName.data()); - AfxMessageBox( msg, MB_OK | MB_ICONINFORMATION); + if ( fInfo->getLockType() != LockNone) { + AfxMessageBox( L"Cannot copy file to Alfresco folder, destination file is locked", MB_OK | MB_ICONEXCLAMATION); + return false; } - else if ( pFileInfo->getLockType() != LockNone) { + else if ( actionInfo.hasPreProcessAction(PreLocalToWorkingCopy) == true && fInfo->isWorkingCopy() == false) { + AfxMessageBox( L"Cannot copy to Alfresco folder, destination must overwrite a working copy", MB_OK | MB_ICONEXCLAMATION); + return false; + } + } + else if ( actionInfo.hasPreProcessAction(PreLocalToWorkingCopy) == true) { - // File is locked, may be the original document + // Target folder does not contain a matching working copy of the local file + + CString msg; + msg.FormatMessage( L"No matching working copy for %1", curName.data()); + AfxMessageBox( msg, MB_OK | MB_ICONEXCLAMATION); + return false; + } + + // Copy the files/folders using the Windows shell + + bool copyAborted = false; + + if ( copyFilesUsingShell( curFile, alfresco.getUNCPath(), copyAborted) == false) { + + // Check if the copy failed or the user aborted the copy + + if ( copyAborted == false) { + + // File copy failed CString msg; - msg.FormatMessage( L"Destination file %1 is locked", curName.data()); + msg.FormatMessage( isDir ? L"Failed to copy folder %1" : L"Failed to copy file %1", curFile.data()); + AfxMessageBox( msg, MB_OK | MB_ICONSTOP); return false; } else { - // Indicate that we have copied a new file to the Alfresco share, do not check in/out + // User aborted the file copy - copyFile = true; + CString msg; + msg.FormatMessage( L"Copy aborted for %1", curFile.data()); + AfxMessageBox( msg, MB_OK | MB_ICONSTOP); + return false; } } - else { - // Indicate that we have copied a new file to the Alfresco share, do not check in/out + // Add a desktop target for the copied file - copyFile = true; - } - - // Build the from/to paths, must be double null terminated - - wchar_t fromPath[MAX_PATH + 1]; - wchar_t toPath[MAX_PATH + 1]; - - memset( fromPath, 0, sizeof( fromPath)); - memset( toPath, 0, sizeof( toPath)); - - wcscpy( fromPath, curFile.data()); - wcscpy( toPath, alfresco.getUNCPath()); - - // Copy the local file to the Alfresco folder - - SHFILEOPSTRUCT fileOpStruct; - memset( &fileOpStruct, 0, sizeof(SHFILEOPSTRUCT)); - - fileOpStruct.hwnd = HWND_DESKTOP; - fileOpStruct.wFunc = FO_COPY; - fileOpStruct.pFrom = fromPath; - fileOpStruct.pTo = toPath; - fileOpStruct.fFlags= 0; - fileOpStruct.fAnyOperationsAborted =false; - - // Copy the file to the Alfresco folder - - if ( SHFileOperation( &fileOpStruct) != 0) { - - // File copy failed - - CString msg; - msg.FormatMessage( L"Failed to copy file %1", curFile.data()); - AfxMessageBox( msg, MB_OK | MB_ICONSTOP); - return false; - } - else if ( fileOpStruct.fAnyOperationsAborted) { - - // User aborted the file copy - - CString msg; - msg.FormatMessage( L"Copy aborted for %1", curFile.data()); - AfxMessageBox( msg, MB_OK | MB_ICONSTOP); - return false; - } - - // Get the file information for the copied file - - pFileInfo = alfresco.getFileInformation( curName); + params.addTarget( new DesktopTarget(isDir ? TargetCopiedFolder : TargetCopiedFile, curName)); } + else { - // Check in or check out the file + // Path is a UNC path, check if the file/folder is in the same folder as the action - if ( pFileInfo.get() != NULL) { + DesktopTarget* pTarget = NULL; - // Check if the file should be checked in/out + if ( curFile.startsWith( alfresco.getUNCPath())) { - if ( copyFile == false) { + // Path is in the same folder as the application, or in a sub-folder - // Check if the file is a working copy, if so then check it in + String relPath = curFile.substring( alfresco.getUNCPath().length() + 1); - if ( pFileInfo->isWorkingCopy()) { + if ( relPath.indexOf( L"\\") == -1) { - // Check in the file + // Create a target using the file name only - doCheckIn( alfresco, pFileInfo); + pTarget = new DesktopTarget( isDir ? TargetFolder : TargetFile, relPath); } - else if ( pFileInfo->getLockType() == LockNone) { + } - // Check out the file + // If the target is not valid the file/folder is not in the same folder as the client-side application, + // copy the files/folders to the target folder or use the root relative path to the file/folder - doCheckOut( alfresco, pFileInfo); + if ( pTarget == NULL) { + + // Check if Alfresco files/folders should be copied to the target folder + + if ( actionInfo.hasPreProcessAction(PreCopyToTarget)) { + + // Copy the files/folders using the Windows shell + + bool copyAborted = false; + + if ( copyFilesUsingShell( curFile, alfresco.getUNCPath(), copyAborted) == false) { + + // Check if the copy failed or the user aborted the copy + + if ( copyAborted == false) { + + // File copy failed + + CString msg; + msg.FormatMessage( isDir ? L"Failed to copy folder %1" : L"Failed to copy file %1", curFile.data()); + + AfxMessageBox( msg, MB_OK | MB_ICONSTOP); + return false; + } + else { + + // User aborted the file copy + + CString msg; + msg.FormatMessage( L"Copy aborted for %1", curFile.data()); + AfxMessageBox( msg, MB_OK | MB_ICONSTOP); + return false; + } + } + + // Add a desktop target for the copied file + + pTarget= new DesktopTarget(isDir ? TargetCopiedFolder : TargetCopiedFile, curName); } else { - // File is locked, may already be checked out + // Get the root relative path to the file/folder - CString msg; - msg.FormatMessage( L"File %1 is locked", curFile.data()); - AfxMessageBox( msg, MB_OK | MB_ICONSTOP); + String rootRelPath = curFile.substring(alfresco.getRootPath().length()); + pTarget = new DesktopTarget( isDir ? TargetFolder : TargetFile, rootRelPath); } } - else { - // No existing file to link the copied file to - - CString msg; - msg.FormatMessage( L"Copied file %1 to Alfresco folder", curFile.data()); - AfxMessageBox( msg, MB_OK | MB_ICONINFORMATION); - } + // Add the desktop target + params.addTarget( pTarget); } - else { - CString msg; - msg.FormatMessage( L"Failed to get file status for %1", curFile.data()); - AfxMessageBox( msg, MB_OK | MB_ICONSTOP); - } - } - else { - - // Check the error status - - CString msg; - - if ( attr != INVALID_FILE_ATTRIBUTES) - msg.FormatMessage( L"Path %1 is a folder, ignored", curFile.data()); - else - msg.FormatMessage( L"File %1 does not exist", curFile.data()); - AfxMessageBox( msg, MB_OK | MB_ICONSTOP); } } @@ -433,74 +394,181 @@ bool CCAlfrescoAppApp::doCheckInOut( AlfrescoInterface& alfresco, StringList& fi } /** - * Check in the specified file + * Copy a file/folder using the Windows shell * - * @param alfresco AlfrescoInterface& - * @param pFileInfo PTR_AlfrescoFileInfo& + * @param fromFileFolder const String& + * @param toFolder const String& + * @param aborted bool& * @return bool */ -bool CCAlfrescoAppApp::doCheckIn( AlfrescoInterface& alfresco, PTR_AlfrescoFileInfo& pFileInfo) { +bool CCAlfrescoAppApp::copyFilesUsingShell(const String& fromFileFolder, const String& toFolder, bool& aborted) { - bool checkedIn = false; + // Build the from/to paths, must be double null terminated - try { + wchar_t fromPath[MAX_PATH + 1]; + wchar_t toPath[MAX_PATH + 1]; - // Check in the specified file + memset( fromPath, 0, sizeof( fromPath)); + memset( toPath, 0, sizeof( toPath)); - alfresco.checkIn( pFileInfo->getName()); + wcscpy( fromPath, fromFileFolder.data()); + wcscpy( toPath, toFolder.data()); - CString msg; - msg.FormatMessage( L"Checked in file %1", pFileInfo->getName().data()); - AfxMessageBox( msg, MB_OK | MB_ICONINFORMATION); + // Copy the local file to the Alfresco folder - // Indicate that the check in was successful + SHFILEOPSTRUCT fileOpStruct; + memset( &fileOpStruct, 0, sizeof(SHFILEOPSTRUCT)); - checkedIn = true; + fileOpStruct.hwnd = HWND_DESKTOP; + fileOpStruct.wFunc = FO_COPY; + fileOpStruct.pFrom = fromPath; + fileOpStruct.pTo = toPath; + fileOpStruct.fFlags= 0; + fileOpStruct.fAnyOperationsAborted =false; + + // Copy the file to the Alfresco folder + + bool sts = false; + + if ( SHFileOperation( &fileOpStruct) == 0) { + + // File copy successful + + sts = true; } - catch (Exception ex) { - CString msg; - msg.FormatMessage( L"Error checking in file %1\n\n%2", pFileInfo->getName().data(), ex.getMessage().data()); - AfxMessageBox( msg, MB_OK | MB_ICONSTOP); + else if ( fileOpStruct.fAnyOperationsAborted) { + + // User aborted the file copy + + aborted = true; } - // Return the check in status + // Return the copy status - return checkedIn; + return sts; } /** - * Check out the specified file + * Run an action * * @param alfresco AlfrescoInterface& - * @param pFileInfo PTR_AlfrescoFileInfo& + * @param pathList StringList& + * @param actionInfo AlfrescoActionInfo& * @return bool */ -bool CCAlfrescoAppApp::doCheckOut( AlfrescoInterface& alfresco, PTR_AlfrescoFileInfo& pFileInfo) { +bool CCAlfrescoAppApp::runAction( AlfrescoInterface& alfresco, StringList& pathList, AlfrescoActionInfo& actionInfo) { - bool checkedOut = false; + // Build the desktop action parameter list, perform any file copying of local files - try { + bool sts = false; + DesktopParams desktopParams; + + if ( buildDesktopParameters( alfresco, pathList, actionInfo, desktopParams)) { - // Check out the specified file + // Check if the action requires parameters - String workingCopy; - alfresco.checkOut( pFileInfo->getName(), workingCopy); + if ( actionInfo.allowsNoParameters() == false && desktopParams.numberOfTargets() == 0) { + AfxMessageBox( L"No parameters for action", MB_OK | MB_ICONEXCLAMATION); + return false; + } - CString msg; - msg.FormatMessage( L"Checked out file %1 to %2", pFileInfo->getName().data(), workingCopy.data()); - AfxMessageBox( msg, MB_OK | MB_ICONINFORMATION); + // Run the desktop action - // Indicate that the check out was successful + DesktopResponse response = alfresco.runAction( actionInfo, desktopParams); - checkedOut = true; - } - catch (Exception ex) { - CString msg; - msg.FormatMessage( L"Error checking out file %1\n\n%2", pFileInfo->getName().data(), ex.getMessage().data()); - AfxMessageBox( msg, MB_OK | MB_ICONSTOP); + // Check the response status + + if ( response.getStatus() != StsSuccess) { + + // Check if the status indicates a command line should be launched + + if ( response.getStatus() == StsCommandLine) { + + // Initialize the startup information + + STARTUPINFO startupInfo; + memset(&startupInfo, 0, sizeof(STARTUPINFO)); + + // Launch a process using the command line + + PROCESS_INFORMATION processInfo; + memset(&processInfo, 0, sizeof(PROCESS_INFORMATION)); + + if ( CreateProcess( response.getStatusMessage().data(), NULL, NULL, NULL, true, 0, NULL, NULL, + &startupInfo, &processInfo) == false) { + CString msg; + msg.FormatMessage( L"Failed to launch command line\n\n%1\n\nError %2!d!", response.getStatusMessage().data(), GetLastError()); + AfxMessageBox( msg, MB_OK | MB_ICONERROR); + } + else + sts = true; + } + + // Check if a web browser should be launched with a URL + + else if ( response.getStatus() == StsLaunchURL) { + + // Use the Windows shell to open the URL + + HINSTANCE shellSts = ShellExecute( NULL, NULL, response.getStatusMessage().data(), NULL, NULL, SW_SHOWNORMAL); + if (( int) shellSts < 32) { + CString msg; + msg.FormatMessage( L"Failed to launch URL\n\n%1", response.getStatusMessage().data()); + AfxMessageBox( msg, MB_OK | MB_ICONERROR); + } + else + sts = true; + } + + // Error status + + else { + + // Get the error message + + String errMsg; + + switch ( response.getStatus()) { + case StsFileNotFound: + errMsg = L"File not found"; + break; + case StsAccessDenied: + errMsg = L"Access denied"; + break; + case StsBadParameter: + errMsg = L"Bad parameter in request"; + break; + case StsNoSuchAction: + errMsg = L"No such action"; + break; + default: + errMsg = L"Error running action"; + break; + } + + // Display an error dialog + + CString msg; + + if ( response.hasStatusMessage()) + msg.FormatMessage( L"%1\n\n%2", errMsg.data(), response.getStatusMessage().data()); + else + msg = errMsg.data(); + + AfxMessageBox( msg, MB_OK | MB_ICONERROR); + } + } + else if ( response.hasStatusMessage()) { + + // Display the message returned by the action + + CString msg; + msg.FormatMessage( L"Action returned message\n\n%1", response.getStatusMessage().data()); + AfxMessageBox( msg, MB_OK | MB_ICONINFORMATION); + } } - // Return the check out status + // Return the action status - return checkedOut; + return sts; } diff --git a/source/cpp/CAlfrescoApp/CAlfrescoApp.h b/source/cpp/CAlfrescoApp/CAlfrescoApp.h index 2a5c21b646..39ac0c6018 100644 --- a/source/cpp/CAlfrescoApp/CAlfrescoApp.h +++ b/source/cpp/CAlfrescoApp/CAlfrescoApp.h @@ -26,6 +26,7 @@ // Includes #include "alfresco\Alfresco.hpp" +#include "alfresco\Desktop.hpp" using namespace Alfresco; @@ -49,10 +50,15 @@ public: private: // Main Alfresco interface functions - bool doFolderStatus( AlfrescoInterface& alfresco, const wchar_t* fileSpec = L"*.*"); - bool doCheckInOut( AlfrescoInterface& alfresco, StringList& files); - bool doCheckIn( AlfrescoInterface& alfresco, PTR_AlfrescoFileInfo& fileInfo); - bool doCheckOut( AlfrescoInterface& alfresco, PTR_AlfrescoFileInfo& fileInfo); + bool buildDesktopParameters( AlfrescoInterface& alfresco, StringList& paths, AlfrescoActionInfo& actionInfo, DesktopParams& params); + + // Copy files/folders using the Windows shell + + bool copyFilesUsingShell(const String& fromPath, const String& toPath, bool& aborted); + + // Run the action + + bool runAction( AlfrescoInterface& alfresco, StringList& pathList, AlfrescoActionInfo& actionInfo); }; extern CCAlfrescoAppApp theApp; \ No newline at end of file diff --git a/source/cpp/CAlfrescoApp/CAlfrescoApp.ncb b/source/cpp/CAlfrescoApp/CAlfrescoApp.ncb index 39edd3a847..04bbbb3bed 100644 Binary files a/source/cpp/CAlfrescoApp/CAlfrescoApp.ncb and b/source/cpp/CAlfrescoApp/CAlfrescoApp.ncb differ diff --git a/source/cpp/CAlfrescoApp/CAlfrescoApp.rc b/source/cpp/CAlfrescoApp/CAlfrescoApp.rc index b7dde5c0e6..ce5908d0ca 100644 --- a/source/cpp/CAlfrescoApp/CAlfrescoApp.rc +++ b/source/cpp/CAlfrescoApp/CAlfrescoApp.rc @@ -27,29 +27,25 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // IDD_ABOUTBOX DIALOGEX 0, 0, 235, 55 -STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | - WS_SYSMENU +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "About CAlfrescoApp" FONT 8, "MS Shell Dlg", 0, 0, 0x1 BEGIN ICON 128,IDC_STATIC,11,17,20,20 - LTEXT "CAlfrescoApp Version 1.0",IDC_STATIC,40,10,119,8, - SS_NOPREFIX + LTEXT "CAlfrescoApp Version 1.0",IDC_STATIC,40,10,119,8,SS_NOPREFIX LTEXT "Copyright (C) 2005",IDC_STATIC,40,25,119,8 DEFPUSHBUTTON "OK",IDOK,178,7,50,16,WS_GROUP END IDD_CALFRESCOAPP_DIALOG DIALOGEX 0, 0, 469, 156 -STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_VISIBLE | - WS_CAPTION | WS_SYSMENU +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_APPWINDOW CAPTION "Alfresco Check In/Out" FONT 8, "MS Shell Dlg", 0, 0, 0x1 BEGIN PUSHBUTTON "OK",IDOK,209,130,50,13 LTEXT "Checked in 99 files",IDC_MSGTEXT,25,22,418,8 - LISTBOX IDC_FILELIST,23,38,424,83,LBS_SORT | - LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP + LISTBOX IDC_FILELIST,23,38,424,83,LBS_SORT | LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP END @@ -83,13 +79,13 @@ BEGIN BLOCK "040904e4" BEGIN VALUE "CompanyName", "Alfresco" - VALUE "FileDescription", "Alfresco Check In/Out" - VALUE "FileVersion", "1.0.0.1" + VALUE "FileDescription", "Alfresco Drag And Drop" + VALUE "FileVersion", "1.0.0.2" VALUE "InternalName", "CAlfrescoApp.exe" VALUE "LegalCopyright", "(c) Alfresco. All rights reserved." VALUE "OriginalFilename", "CAlfrescoApp.exe" VALUE "ProductName", "Alfresco" - VALUE "ProductVersion", "1.0.0.1" + VALUE "ProductVersion", "1.0.0.2" END END BLOCK "VarFileInfo" @@ -149,6 +145,40 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK #pragma code_page(1252) #endif //_WIN32 +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_FILESTATUS DIALOGEX 0, 0, 448, 332 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Alfresco File Status" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + DEFPUSHBUTTON "OK",IDOK,391,311,50,14 + CONTROL "",IDC_FILELIST,"SysListView32",LVS_REPORT | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,7,7,434,299 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_FILESTATUS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 441 + TOPMARGIN, 7 + BOTTOMMARGIN, 325 + END +END +#endif // APSTUDIO_INVOKED + + #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // @@ -193,42 +223,6 @@ END // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. IDI_ICON1 ICON "alfresco.ico" - -///////////////////////////////////////////////////////////////////////////// -// -// Dialog -// - -IDD_FILESTATUS DIALOGEX 0, 0, 448, 332 -STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | - WS_SYSMENU -CAPTION "Alfresco File Status" -FONT 8, "MS Shell Dlg", 400, 0, 0x1 -BEGIN - DEFPUSHBUTTON "OK",IDOK,391,311,50,14 - CONTROL "",IDC_FILELIST,"SysListView32",LVS_REPORT | - LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,7,7,434,299 -END - - -///////////////////////////////////////////////////////////////////////////// -// -// DESIGNINFO -// - -#ifdef APSTUDIO_INVOKED -GUIDELINES DESIGNINFO -BEGIN - IDD_FILESTATUS, DIALOG - BEGIN - LEFTMARGIN, 7 - RIGHTMARGIN, 441 - TOPMARGIN, 7 - BOTTOMMARGIN, 325 - END -END -#endif // APSTUDIO_INVOKED - #endif // English (U.K.) resources ///////////////////////////////////////////////////////////////////////////// diff --git a/source/cpp/CAlfrescoApp/CAlfrescoApp.sln b/source/cpp/CAlfrescoApp/CAlfrescoApp.sln index 655f0d3159..a7e2ec537a 100644 --- a/source/cpp/CAlfrescoApp/CAlfrescoApp.sln +++ b/source/cpp/CAlfrescoApp/CAlfrescoApp.sln @@ -1,21 +1,19 @@ -Microsoft Visual Studio Solution File, Format Version 8.00 +Microsoft Visual Studio Solution File, Format Version 9.00 +# Visual Studio 2005 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CAlfrescoApp", "CAlfrescoApp.vcproj", "{055DCC85-2D1A-4594-B2BE-ED292D2BF26D}" - ProjectSection(ProjectDependencies) = postProject - EndProjectSection EndProject Global - GlobalSection(SolutionConfiguration) = preSolution - Debug = Debug - Release = Release + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 EndGlobalSection - GlobalSection(ProjectConfiguration) = postSolution - {055DCC85-2D1A-4594-B2BE-ED292D2BF26D}.Debug.ActiveCfg = Debug|Win32 - {055DCC85-2D1A-4594-B2BE-ED292D2BF26D}.Debug.Build.0 = Debug|Win32 - {055DCC85-2D1A-4594-B2BE-ED292D2BF26D}.Release.ActiveCfg = Release|Win32 - {055DCC85-2D1A-4594-B2BE-ED292D2BF26D}.Release.Build.0 = Release|Win32 + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {055DCC85-2D1A-4594-B2BE-ED292D2BF26D}.Debug|Win32.ActiveCfg = Debug|Win32 + {055DCC85-2D1A-4594-B2BE-ED292D2BF26D}.Debug|Win32.Build.0 = Debug|Win32 + {055DCC85-2D1A-4594-B2BE-ED292D2BF26D}.Release|Win32.ActiveCfg = Release|Win32 + {055DCC85-2D1A-4594-B2BE-ED292D2BF26D}.Release|Win32.Build.0 = Release|Win32 EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - EndGlobalSection - GlobalSection(ExtensibilityAddIns) = postSolution + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE EndGlobalSection EndGlobal diff --git a/source/cpp/CAlfrescoApp/CAlfrescoApp.suo b/source/cpp/CAlfrescoApp/CAlfrescoApp.suo index 75201f26f0..80b78368a5 100644 Binary files a/source/cpp/CAlfrescoApp/CAlfrescoApp.suo and b/source/cpp/CAlfrescoApp/CAlfrescoApp.suo differ diff --git a/source/cpp/CAlfrescoApp/CAlfrescoApp.vcproj b/source/cpp/CAlfrescoApp/CAlfrescoApp.vcproj index 371ce42ead..2561157e99 100644 --- a/source/cpp/CAlfrescoApp/CAlfrescoApp.vcproj +++ b/source/cpp/CAlfrescoApp/CAlfrescoApp.vcproj @@ -1,127 +1,191 @@ + Keyword="MFCProj" + > + Name="Win32" + /> + + + CharacterSet="1" + > + + + + + + Detect64BitPortabilityProblems="true" + DebugInformationFormat="4" + /> + Name="VCManagedResourceCompilerTool" + /> + + + TargetMachine="1" + /> + Name="VCALinkTool" + /> + Name="VCManifestTool" + /> + Name="VCXDCMakeTool" + /> + Name="VCBscMakeTool" + /> + Name="VCFxCopTool" + /> + Name="VCAppVerifierTool" + /> + Name="VCWebDeploymentTool" + /> - - + Name="VCPostBuildEventTool" + /> + CharacterSet="1" + > + + + + + + Detect64BitPortabilityProblems="true" + DebugInformationFormat="3" + /> + Name="VCManagedResourceCompilerTool" + /> + + + TargetMachine="1" + /> + Name="VCALinkTool" + /> + Name="VCManifestTool" + /> + Name="VCXDCMakeTool" + /> + Name="VCBscMakeTool" + /> + Name="VCFxCopTool" + /> + Name="VCAppVerifierTool" + /> + Name="VCWebDeploymentTool" + /> - - + Name="VCPostBuildEventTool" + /> @@ -130,165 +194,227 @@ + UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}" + > + RelativePath=".\CAlfrescoApp.cpp" + > + RelativePath=".\CAlfrescoAppDlg.cpp" + > - - + RelativePath=".\stdafx.cpp" + > + Name="Debug|Win32" + > + UsePrecompiledHeader="1" + /> + Name="Release|Win32" + > + UsePrecompiledHeader="1" + /> + > + RelativePath=".\source\util\ByteArray.cpp" + > + RelativePath=".\source\util\DataBuffer.cpp" + > + RelativePath=".\source\util\DataPacker.cpp" + > + RelativePath=".\source\util\Exception.cpp" + > + RelativePath=".\source\util\FileName.cpp" + > + RelativePath=".\source\util\Integer.cpp" + > + RelativePath=".\source\util\Long.cpp" + > + RelativePath=".\source\util\String.cpp" + > + RelativePath=".\source\util\System.cpp" + > + > + RelativePath=".\source\alfresco\Alfresco.cpp" + > + + + UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}" + > + RelativePath=".\CAlfrescoApp.h" + > + RelativePath=".\CAlfrescoAppDlg.h" + > + RelativePath=".\Resource.h" + > - - + RelativePath=".\stdafx.h" + > + > + RelativePath=".\includes\util\ByteArray.h" + > + RelativePath=".\includes\util\DataBuffer.h" + > + RelativePath=".\includes\util\DataPacker.h" + > + RelativePath=".\includes\util\Exception.h" + > + RelativePath=".\includes\util\FileName.h" + > + RelativePath=".\includes\util\Integer.h" + > + RelativePath=".\includes\util\JavaTypes.h" + > + RelativePath=".\includes\util\Long.h" + > + RelativePath=".\includes\util\String.h" + > + RelativePath=".\includes\util\System.h" + > + RelativePath=".\includes\util\Types.h" + > + > + RelativePath=".\includes\alfresco\Alfresco.hpp" + > + + + UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}" + > + RelativePath=".\alfresco.ico" + > + RelativePath=".\res\CAlfrescoApp.ico" + > + RelativePath=".\CAlfrescoApp.rc" + > + RelativePath=".\res\CAlfrescoApp.rc2" + > + DeploymentContent="true" + > + RelativePath=".\res\CAlfrescoApp.manifest" + > + + + + + + + RelativePath=".\ReadMe.txt" + > + Value="CAlfrescoApp.rc" + /> diff --git a/source/cpp/CAlfrescoApp/FileStatusDialog.cpp b/source/cpp/CAlfrescoApp/FileStatusDialog.cpp deleted file mode 100644 index cddeaf4344..0000000000 --- a/source/cpp/CAlfrescoApp/FileStatusDialog.cpp +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2005-2006 Alfresco, Inc. - * - * Licensed under the Mozilla Public License version 1.1 - * with a permitted attribution clause. You may obtain a - * copy of the License at - * - * http://www.alfresco.org/legal/license.txt - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - * either express or implied. See the License for the specific - * language governing permissions and limitations under the - * License. - */ - -#include "stdafx.h" -#include "CAlfrescoApp.h" -#include "FileStatusDialog.h" - -#include "util\Long.h" - -// CFileStatusDialog dialog - -IMPLEMENT_DYNAMIC(CFileStatusDialog, CDialog) -CFileStatusDialog::CFileStatusDialog(AlfrescoFileInfoList& fileList, CWnd* pParent /*=NULL*/) - : CDialog(CFileStatusDialog::IDD, pParent), - m_fileList( fileList) -{ -} - -CFileStatusDialog::~CFileStatusDialog() -{ -} - -void CFileStatusDialog::DoDataExchange(CDataExchange* pDX) -{ - CDialog::DoDataExchange(pDX); - DDX_Control(pDX, IDC_FILELIST, m_listCtrl); -} - - -BEGIN_MESSAGE_MAP(CFileStatusDialog, CDialog) -END_MESSAGE_MAP() - -/** - * Initialize the dialog - */ -BOOL CFileStatusDialog::OnInitDialog() { - - // Call the base class - - CDialog::OnInitDialog(); - - // Add headers to the list control - - m_listCtrl.InsertColumn( 0, L"Name", LVCFMT_LEFT, 200); - m_listCtrl.InsertColumn( 1, L"Mime-type", LVCFMT_LEFT, 140); - m_listCtrl.InsertColumn( 2, L"Size", LVCFMT_RIGHT, 80); - m_listCtrl.InsertColumn( 3, L"Status", LVCFMT_LEFT, 100); - m_listCtrl.InsertColumn( 4, L"Owner", LVCFMT_LEFT, 100); - - // Add the list view data - - for ( unsigned int i = 0; i < m_fileList.size(); i++) { - - // Get the current file information - - const AlfrescoFileInfo* pInfo = m_fileList.getInfoAt( i); - - // Add the item to the list view - - if ( pInfo != NULL) { - - // Insert a new item in the view - - int nIndex = m_listCtrl.InsertItem( 0, pInfo->getName()); - - if ( pInfo->isType() == TypeFile) { - - // Display the mime-type and content length - - m_listCtrl.SetItemText( nIndex, 1, pInfo->getContentType()); - m_listCtrl.SetItemText( nIndex, 2, Long::toString( pInfo->getContentLength())); - - String status; - String owner; - - if ( pInfo->isWorkingCopy()) { - status = L"Work"; - } - else if ( pInfo->getLockType() != LockNone) { - status = L"Locked"; - owner = pInfo->getLockOwner(); - } - - m_listCtrl.SetItemText( nIndex, 3, status); - m_listCtrl.SetItemText( nIndex, 4, owner); - } - } - } - - // Clear the file info list - - m_fileList.clear(); - - return FALSE; -} - -// CFileStatusDialog message handlers diff --git a/source/cpp/CAlfrescoApp/FileStatusDialog.h b/source/cpp/CAlfrescoApp/FileStatusDialog.h deleted file mode 100644 index 3864e1c1dd..0000000000 --- a/source/cpp/CAlfrescoApp/FileStatusDialog.h +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2005-2006 Alfresco, Inc. - * - * Licensed under the Mozilla Public License version 1.1 - * with a permitted attribution clause. You may obtain a - * copy of the License at - * - * http://www.alfresco.org/legal/license.txt - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - * either express or implied. See the License for the specific - * language governing permissions and limitations under the - * License. - */ - -#pragma once -#include "afxcmn.h" - -#include "alfresco\Alfresco.hpp" - -// CFileStatusDialog dialog - -class CFileStatusDialog : public CDialog -{ - DECLARE_DYNAMIC(CFileStatusDialog) - -public: - CFileStatusDialog( AlfrescoFileInfoList& fileList, CWnd* pParent = NULL); // standard constructor - virtual ~CFileStatusDialog(); - -// Dialog Data - enum { IDD = IDD_FILESTATUS }; - - // Initialize the dialog - - BOOL OnInitDialog(); - -protected: - virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support - - DECLARE_MESSAGE_MAP() - CListCtrl m_listCtrl; - -protected: - // File information list - - AlfrescoFileInfoList& m_fileList; -}; diff --git a/source/cpp/CAlfrescoApp/FileStatusView.cpp b/source/cpp/CAlfrescoApp/FileStatusView.cpp deleted file mode 100644 index 2b552204ce..0000000000 --- a/source/cpp/CAlfrescoApp/FileStatusView.cpp +++ /dev/null @@ -1,40 +0,0 @@ -// FileStatusView.cpp : implementation file -// - -#include "stdafx.h" -#include "CAlfrescoApp.h" -#include "FileStatusView.h" - - -// CFileStatusView - -IMPLEMENT_DYNCREATE(CFileStatusView, CListView) - -CFileStatusView::CFileStatusView() -{ -} - -CFileStatusView::~CFileStatusView() -{ -} - -BEGIN_MESSAGE_MAP(CFileStatusView, CListView) -END_MESSAGE_MAP() - - -// CFileStatusView diagnostics - -#ifdef _DEBUG -void CFileStatusView::AssertValid() const -{ - CListView::AssertValid(); -} - -void CFileStatusView::Dump(CDumpContext& dc) const -{ - CListView::Dump(dc); -} -#endif //_DEBUG - - -// CFileStatusView message handlers diff --git a/source/cpp/CAlfrescoApp/FileStatusView.h b/source/cpp/CAlfrescoApp/FileStatusView.h deleted file mode 100644 index 066f367e55..0000000000 --- a/source/cpp/CAlfrescoApp/FileStatusView.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - - -// CFileStatusView view - -class CFileStatusView : public CListView -{ - DECLARE_DYNCREATE(CFileStatusView) - -protected: - CFileStatusView(); // protected constructor used by dynamic creation - virtual ~CFileStatusView(); - -public: -#ifdef _DEBUG - virtual void AssertValid() const; - virtual void Dump(CDumpContext& dc) const; -#endif - -protected: - DECLARE_MESSAGE_MAP() -}; - - diff --git a/source/cpp/CAlfrescoApp/includes/alfresco/Alfresco.hpp b/source/cpp/CAlfrescoApp/includes/alfresco/Alfresco.hpp index 49d076414b..397bd18831 100644 --- a/source/cpp/CAlfrescoApp/includes/alfresco/Alfresco.hpp +++ b/source/cpp/CAlfrescoApp/includes/alfresco/Alfresco.hpp @@ -25,17 +25,22 @@ #include #include + #include "util\Exception.h" #include "util\String.h" #include "util\DataBuffer.h" +#include "alfresco\Desktop.hpp" + // Classes defined in this header file namespace Alfresco { class AlfrescoInterface; class AlfrescoFileInfo; class AlfrescoFileInfoList; + class AlfrescoActionInfo; typedef std::auto_ptr PTR_AlfrescoFileInfo; + typedef std::auto_ptr PTR_AlfrescoActionInfo; } // Constants @@ -44,10 +49,12 @@ namespace Alfresco { // Alfresco I/O control codes - #define FSCTL_ALFRESCO_PROBE CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS) - #define FSCTL_ALFRESCO_FILESTS CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS) - #define FSCTL_ALFRESCO_CHECKOUT CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 0x802, METHOD_BUFFERED, FILE_WRITE_DATA) - #define FSCTL_ALFRESCO_CHECKIN CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 0x803, METHOD_BUFFERED, FILE_WRITE_DATA) + #define FSCTL_ALFRESCO_PROBE CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS) + #define FSCTL_ALFRESCO_FILESTS CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS) + // Version 1 FSCTL_ALFRESCO_CHECKOUT - 0x802 + // Version 1 FSCTL_ALFRESCO_CHECKIN - 0x803 + #define FSCTL_ALFRESCO_GETACTIONINFO CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 0x804, METHOD_BUFFERED, FILE_WRITE_DATA) + #define FSCTL_ALFRESCO_RUNACTION CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 0x805, METHOD_BUFFERED, FILE_WRITE_DATA) // Request signature bytes @@ -68,6 +75,9 @@ namespace Alfresco { #define StsAccessDenied 3 #define StsBadParameter 4 #define StsNotWorkingCopy 5 + #define StsNoSuchAction 6 + #define StsLaunchURL 7 + #define StsCommandLine 8 // Boolean field values @@ -86,6 +96,27 @@ namespace Alfresco { #define LockNone 0 #define LockRead 1 #define LockWrite 2 + + // Desktop action attributes + + #define AttrTargetFiles 0x0001 + #define AttrTargetFolders 0x0002 + #define AttrClientFiles 0x0004 + #define AttrClientFolders 0x0008 + #define AttrAlfrescoFiles 0x0010 + #define AttrAlfrescoFolders 0x0020 + #define AttrMultiplePaths 0x0040 + #define AttrAllowNoParams 0x0080 + + #define AttrAnyFiles (AttrTargetFiles + AttrClientFiles + AttrAlfrescoFiles) + #define AttrAnyFolders (AttrTargetFolders + AttrClientFolders + AttrAlfrescoFolders) + #define AttrAnyFilesFolders (AttrAnyFiles + AttrAnyFolders) + + // Desktop action pre-processing actions + + #define PreCopyToTarget 0x0001 + #define PreConfirmAction 0x0002 + #define PreLocalToWorkingCopy 0x0004 } // Define Alfresco interface exceptions @@ -122,17 +153,21 @@ public: bool isAlfrescoFolder( void); + // Return the protocol version of the server + + inline const unsigned int isProtocolVersion( void) const { return m_protocolVersion; } + // Return the Alfresco file information for a file/folder within the current folder PTR_AlfrescoFileInfo getFileInformation(const wchar_t* fileName); - // Check in a working copy file + // Get action information, map the executable name to a server action - void checkIn( const wchar_t* fileName, bool keepCheckedOut = false); + AlfrescoActionInfo getActionInformation(const wchar_t* exeName); - // Check out a file + // Run a desktop action and return the server response - void checkOut( const wchar_t* fileName, String& workingCopy); + DesktopResponse runAction(AlfrescoActionInfo& action, DesktopParams& params); private: // Send an I/O control request, receive and validate the response @@ -160,6 +195,9 @@ private: HANDLE m_handle; + // Protocol version + + unsigned int m_protocolVersion; }; /** @@ -292,4 +330,86 @@ private: std::vector m_list; }; +/** + * Alfresco Action Info Class + */ +class Alfresco::AlfrescoActionInfo { +public: + // Default constructor + + AlfrescoActionInfo(void); + + // Class constructor + + AlfrescoActionInfo( const String& name, const unsigned int attr, const unsigned int preActions); + + // Return the action name, pseudo file name + + inline const String& getName(void) const { return m_name; } + inline const String& getPseudoName(void) const { return m_pseudoName; } + + // Return the action attributes, action pre-processing flags + + inline unsigned int getAttributes(void) const { return m_attributes; } + inline unsigned int getPreProcessActions(void) const { return m_clientPreActions; } + + // Check if the action has the specifed attribute/pre-processing action + + inline bool hasAttribute(const unsigned int attr) const { return (m_attributes & attr) != 0 ? true : false; } + inline bool hasPreProcessAction(const unsigned int pre) const { return (m_clientPreActions & pre) != 0 ? true : false; } + + // Check if the confirmation message is valid, return the confirmation message + + inline bool hasConfirmationMessage(void) const { return m_confirmMsg.length() > 0 ? true : false; } + inline const String& getConfirmationMessage(void) const { return m_confirmMsg; } + + // Check if the action supports file or folder paths + + inline bool supportsFiles(void) const { return hasAttribute(AttrTargetFiles+AttrClientFiles+AttrAlfrescoFiles); } + inline bool supportsFolders(void) const { return hasAttribute(AttrTargetFolders+AttrClientFolders+AttrAlfrescoFolders); } + + // Check if the action allows no parameters + + inline bool allowsNoParameters(void) const { return hasAttribute(AttrAllowNoParams) || hasAttribute(AttrAnyFilesFolders) == false; } + + // Set the action name, pseudo name, set the confirmation message + + inline void setName(const String& name) { m_name = name; } + inline void setPseudoName(const String& pseudo) { m_pseudoName = pseudo; } + inline void setConfirmationMessage(const String& msg) { m_confirmMsg = msg; } + + // Set the action attributes and pre-processing actions + + inline void setAttributes(const unsigned int attr) { m_attributes = attr; } + inline void setPreProcessActions(const unsigned int pre) { m_clientPreActions = pre; } + + // Return the action information as a string + + const String toString(void) const; + + // Assignment operator + + AlfrescoActionInfo& operator=( const AlfrescoActionInfo& actionInfo); + +private: + // Instance variables + // + // Action name + + String m_name; + + // Pseudo file name + + String m_pseudoName; + + // Action attributes and pre-processing flags + + unsigned int m_attributes; + unsigned int m_clientPreActions; + + // Action confirmation message + + String m_confirmMsg; +}; + #endif diff --git a/source/cpp/CAlfrescoApp/includes/alfresco/Desktop.hpp b/source/cpp/CAlfrescoApp/includes/alfresco/Desktop.hpp new file mode 100755 index 0000000000..d00f492c0e --- /dev/null +++ b/source/cpp/CAlfrescoApp/includes/alfresco/Desktop.hpp @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2005-2006 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ + +#ifndef _Desktop_H +#define _Desktop_H + +// Includes + +#include + +#include +#include +#include "util\Exception.h" +#include "util\String.h" +#include "util\DataBuffer.h" + +// Classes defined in this header file + +namespace Alfresco { + class DesktopTarget; + class DesktopParams; + class DesktopResponse; + + typedef std::auto_ptr PTR_DesktopTarget; + typedef std::auto_ptr PTR_DesktopParams; + typedef std::auto_ptr PTR_DesktopResponse; +} + +// Constants + +namespace Alfresco { + + // Desktop target types + + #define TargetFile 0 + #define TargetFolder 1 + #define TargetCopiedFile 2 + #define TargetCopiedFolder 3 + #define TargetNodeRef 4 +} + +// Define desktop action exceptions + +DEFINE_EXCEPTION(Alfresco, DesktopActionException); + +/** + * Desktop Target Class + * + * Contains the details of a target for a desktop action. + */ +class Alfresco::DesktopTarget { +public: + // Class constructors + + DesktopTarget(int typ, String& path); + + // Class destructor + + ~DesktopTarget(); + + // Return the target type, target path/id + + inline unsigned int isType(void) const { return m_type; } + inline const String& getTarget(void) const { return m_target; } + + // Return the target type as a string + + const String getTypeAsString( void) const; + + // Return the target details as a string + + const String toString( void) const; + + // Operators + + bool operator==( const DesktopTarget& target); + bool operator<( const DesktopTarget& target); + +private: + // Hide the copy constructor + + DesktopTarget(const DesktopTarget& target) {}; + +private: + // Instance variables + // + // Target type and path/id + + unsigned int m_type; + String m_target; +}; + +/** + * Desktop Params Class + * + * Contains the parameters for a desktop action request. + */ +class Alfresco::DesktopParams { +public: + // Class constructors + + DesktopParams(void) {} + + // Return the number of targets + + inline size_t numberOfTargets(void) const { return m_list.size(); } + + // Return a target from the list + + const DesktopTarget* getTarget(const unsigned int idx) const; + + // Add a desktop target + + inline void addTarget(DesktopTarget* pTarget) { m_list.push_back(pTarget); } + + // Clear the target list + + inline void clearTargets( void) { m_list.clear(); } + + // Return the desktop parameters as a string + + const String toString(void) const; + +private: + // Instance variables + // + // List of file/folder/node targets for the action + + std::vector m_list; +}; + +/** + * Desktop Response Class + * + * Contains the result of calling a server side desktop action. + */ +class Alfresco::DesktopResponse { +public: + // class constructors + + DesktopResponse( const unsigned int sts, const wchar_t* msg = NULL); + + // Return the status code + + inline unsigned int getStatus( void) const { return m_status; } + + // Check if there is a status message, return the status message + + inline bool hasStatusMessage(void) const { return m_statusMsg.length() > 0; } + inline const String& getStatusMessage(void) const { return m_statusMsg; } + + // Assignment operator + + DesktopResponse& operator=( const DesktopResponse& response); + +private: + // Instance variables + // + // Status code and message + + unsigned int m_status; + String m_statusMsg; +}; + +#endif diff --git a/source/cpp/CAlfrescoApp/source/AlfrescoApp.cpp b/source/cpp/CAlfrescoApp/source/AlfrescoApp.cpp deleted file mode 100644 index 1cb9ec954a..0000000000 --- a/source/cpp/CAlfrescoApp/source/AlfrescoApp.cpp +++ /dev/null @@ -1,420 +0,0 @@ -#include - -#include "alfresco\Alfresco.hpp" - -#include "util\String.h" -#include "util\DataBuffer.h" -#include "util\FileName.h" - -#include - -using namespace std; -using namespace JLAN; - -// Function prototypes - -bool doFolderStatus( Alfresco& alfresco, const wchar_t* fileSpec = L"*.*"); -bool doCheckInOut( Alfresco& alfresco, StringList& files); -bool doCheckIn( Alfresco& alfresco, PTR_AlfrescoFileInfo& fileInfo); -bool doCheckOut( Alfresco& alfresco, PTR_AlfrescoFileInfo& fileInfo); - -/** - * Alfresco Windows Drag And Drop Application - * - * @author GKSpencer - */ -int wmain( int argc, wchar_t* argv[], wchar_t* envp[]) { - - // Output a startup banner - - wcout << L"Alfresco Drag And Drop Application" << endl; - wcout << L"----------------------------------" << endl; - - // Check if the app is running from a network drive - - String appPath(argv[0]); -// String appPath("\\\\StarlaA\\Alfresco\\Garys Space\\AlfrescoApp.exe"); -// String appPath("Z:\\Garys Space\\AlfrescoApp.exe"); -// argc = 2; - - // Looks like a UNC path, trim off the application name - - int pos = appPath.lastIndexOf(PathSeperator); - if ( pos < 0) { - wcout << L"%% Invalid application path, " << appPath << endl; - return 1; - } - - // Get the path to the folder containing the application - - String folderPath = appPath.substring(0, pos); - - // Create the Alfresco interface - - Alfresco alfresco(folderPath); - if ( alfresco.isAlfrescoFolder()) { - - // If there are no file paths on the command line then display a status page for the files - // in the Alfresco folder - - if ( argc == 1) { - - // Display status for the files in the Alfresco folder - - doFolderStatus( alfresco); - } - else { - - // Build a list of the file names - - StringList fileList; - - for ( int i = 1; i < argc; i++) - fileList.addString( String(argv[i])); -// fileList.addString(L"N:\\testArea\\msword\\CIFSLOG.doc"); -// fileList.addString(L"\\\\StarlaA\\Alfresco\\Garys Space\\CIFSLOG.doc"); - - // Process the file list and check in or out each file - - doCheckInOut( alfresco, fileList); - } - } - else { - wcout << L"%% Not a valid Alfresco CIFS folder, " << folderPath << endl; - return 1; - } - - // Wait for user input - - wcout << L"Press to continue ..." << flush; - getchar(); -} - -/** - * Display file status of the files in the target Alfresco folder - * - * @param Alfresco& alfresco - * @param const wchar_t* fileSpec - * @return bool - */ -bool doFolderStatus( Alfresco& alfresco, const wchar_t* fileSpec) { - - // Get the base UNC path - - String uncPath = alfresco.getUNCPath(); - uncPath.append(PathSeperator); - - // Search the Alfresco folder - - WIN32_FIND_DATA findData; - String searchPath = uncPath; - searchPath.append( fileSpec); - - bool sts = false; - HANDLE fHandle = FindFirstFile( searchPath, &findData); - - if ( fHandle != INVALID_HANDLE_VALUE) { - - // Loop until all files have been returned - - PTR_AlfrescoFileInfo pFileInfo; - sts = true; - - while ( fHandle != INVALID_HANDLE_VALUE) { - - // Get the file name, ignore the '.' and '..' files - - String fName = findData.cFileName; - - if ( fName.equals(L".") || fName.equals(L"..")) { - - // Get the next file/folder name in the search - - if ( FindNextFile( fHandle, &findData) == 0) - fHandle = INVALID_HANDLE_VALUE; - continue; - } - - // Get the file information for the current file folder - - pFileInfo = alfresco.getFileInformation( findData.cFileName); - - if ( pFileInfo.get() != NULL) { - - // Output the file details - - wcout << pFileInfo->getName() << endl; - - if ( pFileInfo->isType() == TypeFolder) - wcout << L" [Folder]" << endl; - - if ( pFileInfo->isWorkingCopy()) - wcout << L" [Work: " << pFileInfo->getCopyOwner() << L", " << pFileInfo->getCopiedFrom() << L"]" << endl; - - if ( pFileInfo->getLockType() != LockNone) - wcout << L" [Lock: " << (pFileInfo->getLockType() == LockRead ? L"READ" : L"WRITE") << L", " << - pFileInfo->getLockOwner() << L"]" << endl; - - if ( pFileInfo->hasContent()) - wcout << L" [Content: " << pFileInfo->getContentLength() << L", " << pFileInfo->getContentType() << L"]" << endl;; - - // Get the next file/folder name in the search - - if ( FindNextFile( fHandle, &findData) == 0) - fHandle = INVALID_HANDLE_VALUE; - } - else { - sts = false; - fHandle = INVALID_HANDLE_VALUE; - } - } - } - - // Return status - - return sts; -} - -/** - * Process the list of files and check in or out each file - * - * @param alfresco Alfresco& - * @param files StringList& - */ -bool doCheckInOut( Alfresco& alfresco, StringList& files) { - - // Process the list of files and either check in the file if it is a working copy or check out - // the file - - for ( unsigned int i = 0; i < files.numberOfStrings(); i++) { - - // Get the current file name - - String curFile = files.getStringAt( i); - - // Check if the path is on an Alfresco mapped drive - - if ( alfresco.isMappedDrive() && curFile.startsWithIgnoreCase( alfresco.getDrivePath())) { - - // Convert the path to a UNC path - - String uncPath = alfresco.getRootPath(); - uncPath.append( curFile.substring(2)); - - curFile = uncPath; - } - - // Check that the path is to a file - - bool copyFile = false; - - DWORD attr = GetFileAttributes( curFile); - if ( attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY) == 0) { - - // Get the file name from the path - - StringList nameParts = FileName::splitPath( curFile); - String curName = nameParts.getStringAt( 1); - - // Get the Alfresco file status information - - PTR_AlfrescoFileInfo pFileInfo = alfresco.getFileInformation( curName); - - // If the path is to a file that is not on the Alfresco share the file will need to be copied, - // after checking the status of a matching file in the Alfresco folder - - if ( curFile.length() >= 3 && curFile.substring(1,3).equals( L":\\")) { - - // Check if there is an existing file with the same name - - if ( pFileInfo.get() != NULL) { - - // Check if the file is a working copy - - if ( pFileInfo->isWorkingCopy()) { - - // Local file matches a working copy file in the Alfresco folder - - wcout << L"Found matching working copy for local file " << curName << endl; - } - else if ( pFileInfo->getLockType() != LockNone) { - - // File is locked, may be the original document - - wcout << L"%% Destination file " << curName << L" is locked" << endl; - return false; - } - else { - - // Indicate that we have copied a new file to the Alfresco share, do not check in/out - - copyFile = true; - } - } - else { - - // Indicate that we have copied a new file to the Alfresco share, do not check in/out - - copyFile = true; - } - - // Build the from/to paths, must be double null terminated - - wchar_t fromPath[MAX_PATH + 1]; - wchar_t toPath[MAX_PATH + 1]; - - memset( fromPath, 0, sizeof( fromPath)); - memset( toPath, 0, sizeof( toPath)); - - wcscpy( fromPath, curFile.data()); - wcscpy( toPath, alfresco.getUNCPath()); - - // Copy the local file to the Alfresco folder - - SHFILEOPSTRUCT fileOpStruct; - memset( &fileOpStruct, 0, sizeof(SHFILEOPSTRUCT)); - - fileOpStruct.hwnd = HWND_DESKTOP; - fileOpStruct.wFunc = FO_COPY; - fileOpStruct.pFrom = fromPath; - fileOpStruct.pTo = toPath; - fileOpStruct.fFlags= 0; - fileOpStruct.fAnyOperationsAborted =false; - - // Copy the file to the Alfresco folder - - if ( SHFileOperation( &fileOpStruct) != 0) { - - // File copy failed - - wcout << L"%% Failed to copy file " << curFile << endl; - return false; - } - else if ( fileOpStruct.fAnyOperationsAborted) { - - // User aborted the file copy - - wcout << L"%% Copy aborted for " << curFile << endl; - return false; - } - - // Get the file information for the copied file - - pFileInfo = alfresco.getFileInformation( curName); - } - - // Check in or check out the file - - if ( pFileInfo.get() != NULL) { - - // Check if the file should be checked in/out - - if ( copyFile == false) { - - // Check if the file is a working copy, if so then check it in - - if ( pFileInfo->isWorkingCopy()) { - - // Check in the file - - doCheckIn( alfresco, pFileInfo); - } - else if ( pFileInfo->getLockType() == LockNone) { - - // Check out the file - - doCheckOut( alfresco, pFileInfo); - } - else { - - // File is locked, may already be checked out - - wcout << L"%% File " << curFile << L" is locked" << endl; - } - } - else { - - // No existing file to link the copied file to - - wcout << L"Copied file " << curFile << L" to Alfresco share" << endl; - } - - } - else - wcout << L"%% Failed to get file status for " << curFile << endl; - } - else - wcout << L"%% Path " << curFile << L" is a folder, ignored" << endl; - } - - // Return status - - return true; -} - -/** - * Check in the specified file - * - * @param alfresco Alfresco& - * @param pFileInfo PTR_AlfrescoFileInfo& - * @return bool - */ -bool doCheckIn( Alfresco& alfresco, PTR_AlfrescoFileInfo& pFileInfo) { - - bool checkedIn = false; - - try { - - // Check in the specified file - - alfresco.checkIn( pFileInfo->getName()); - - wcout << L"Checked in file " << pFileInfo->getName() << endl; - - // Indicate that the check in was successful - - checkedIn = true; - } - catch (Exception ex) { - wcerr << L"%% Error checking in file " << pFileInfo->getName() << endl; - wcerr << L" " << ex.getMessage() << endl; - } - - // Return the check in status - - return checkedIn; -} - -/** - * Check out the specified file - * - * @param alfresco Alfresco& - * @param pFileInfo PTR_AlfrescoFileInfo& - * @return bool - */ -bool doCheckOut( Alfresco& alfresco, PTR_AlfrescoFileInfo& pFileInfo) { - - bool checkedOut = false; - - try { - - // Check out the specified file - - String workingCopy; - alfresco.checkOut( pFileInfo->getName(), workingCopy); - - wcout << L"Checked out file " << pFileInfo->getName() << " to " << workingCopy << endl; - - // Indicate that the check out was successful - - checkedOut = true; - } - catch (Exception ex) { - wcerr << L"%% Error checking out file " << pFileInfo->getName() << endl; - wcerr << L" " << ex.getMessage() << endl; - } - - // Return the check out status - - return checkedOut; -} diff --git a/source/cpp/CAlfrescoApp/source/alfresco/Alfresco.cpp b/source/cpp/CAlfrescoApp/source/alfresco/Alfresco.cpp index ae0cbe6f7f..31f08bcf3e 100644 --- a/source/cpp/CAlfrescoApp/source/alfresco/Alfresco.cpp +++ b/source/cpp/CAlfrescoApp/source/alfresco/Alfresco.cpp @@ -40,6 +40,10 @@ AlfrescoInterface::AlfrescoInterface(String& path) { m_handle = INVALID_HANDLE_VALUE; + // Default the protocol version + + m_protocolVersion = 1; + // Check if the path is to a mapped drive String alfPath = path; @@ -136,8 +140,17 @@ bool AlfrescoInterface::isAlfrescoFolder( void) { bool alfFolder = false; try { + + // Check if the remote server is an Alfresco CIFS server + sendIOControl( FSCTL_ALFRESCO_PROBE, reqbuf, respbuf); alfFolder = true; + + // Get the protocol version, if available + + respbuf.getInt(); // status + if ( respbuf.getAvailableLength() >= 4) + m_protocolVersion = respbuf.getInt(); } catch ( Exception ex) { } @@ -229,102 +242,102 @@ PTR_AlfrescoFileInfo AlfrescoInterface::getFileInformation( const wchar_t* fileN } /** - * Check in a working copy file - * - * @param fileName const wchar_t* - * @param keepCheckedOut bool - */ -void AlfrescoInterface::checkIn( const wchar_t* fileName, bool keepCheckedOut) { +* Return Alfresco action information for the specified executable +* +* @param fileName const wchar_t* +* @return AlfrescoActionInfo +*/ +AlfrescoActionInfo AlfrescoInterface::getActionInformation( const wchar_t* exeName) { // Check if the folder handle is valid if ( m_handle == INVALID_HANDLE_VALUE) throw BadInterfaceException(); - // Build the file information I/O control request + // Build the action information I/O control request DataBuffer reqbuf( 256); - DataBuffer respbuf( 128); + DataBuffer respbuf( 512); reqbuf.putFixedString( IOSignature, IOSignatureLen); - reqbuf.putString( fileName); - reqbuf.putInt( keepCheckedOut ? True : False); + reqbuf.putString( exeName); - sendIOControl( FSCTL_ALFRESCO_CHECKIN, reqbuf, respbuf); + sendIOControl( FSCTL_ALFRESCO_GETACTIONINFO, reqbuf, respbuf); - // Get the status code + // Unpack the request status - unsigned int stsCode = respbuf.getInt(); - if ( stsCode == StsSuccess) - return; - else { + AlfrescoActionInfo actionInfo; - // Get the error message, if available + unsigned int reqSts = respbuf.getInt(); + if ( reqSts == StsSuccess) { - String errMsg; + // Unpack the action name, attributes and pre-action flags - if ( respbuf.getAvailableLength() > 0) - errMsg = respbuf.getString(); - else { - errMsg = "Error code "; - errMsg.append( Integer::toString( stsCode)); - } + String name = respbuf.getString(); + unsigned int attr = respbuf.getInt(); + unsigned int preActions = respbuf.getInt(); + String confirmMsg = respbuf.getString(); - // Throw an exception + // Create the action information - throw Exception( errMsg); + actionInfo.setName(name); + actionInfo.setAttributes(attr); + actionInfo.setPreProcessActions(preActions); + actionInfo.setPseudoName( exeName); + actionInfo.setConfirmationMessage( confirmMsg); } + + // Return the action information + + return actionInfo; } /** - * Check out a file and return the working copy file name + * Run a desktop action * - * @param fileName const wchar_t* - * @param workingCopy String& + * @param action AlfrescoActionInfo& + * @param params DesktopParams& + * @return DesktopResponse */ -void AlfrescoInterface::checkOut( const wchar_t* fileName, String& workingCopy) { +DesktopResponse AlfrescoInterface::runAction(AlfrescoActionInfo& action, DesktopParams& params) { // Check if the folder handle is valid if ( m_handle == INVALID_HANDLE_VALUE) throw BadInterfaceException(); - // Build the file information I/O control request + // Build the run action I/O control request - DataBuffer reqbuf( 256); + DataBuffer reqbuf( 1024); DataBuffer respbuf( 256); reqbuf.putFixedString( IOSignature, IOSignatureLen); - reqbuf.putString( fileName); + reqbuf.putString( action.getName()); + reqbuf.putInt((unsigned int)params.numberOfTargets()); - sendIOControl( FSCTL_ALFRESCO_CHECKOUT, reqbuf, respbuf); + for ( unsigned int i = 0; i < params.numberOfTargets(); i++) { - // Get the status code + // Pack the current target details - unsigned int stsCode = respbuf.getInt(); - if ( stsCode == StsSuccess) { + const DesktopTarget* pTarget = params.getTarget(i); - // Get the working copy file name - - workingCopy = respbuf.getString(); + reqbuf.putInt(pTarget->isType()); + reqbuf.putString(pTarget->getTarget()); } - else { - // Get the error message, if available + // Send the run action request - String errMsg; + sendIOControl( FSCTL_ALFRESCO_RUNACTION, reqbuf, respbuf); - if ( respbuf.getAvailableLength() > 0) - errMsg = respbuf.getString(); - else { - errMsg = "Error code "; - errMsg.append( Integer::toString( stsCode)); - } + // Unpack the run action response - // Throw an exception + unsigned int actionSts = respbuf.getInt(); + String actionMsg = respbuf.getString(); - throw Exception( errMsg); - } + // Return the desktop response + + DesktopResponse response(actionSts, actionMsg); + return response; } /** @@ -439,3 +452,67 @@ bool AlfrescoFileInfo::operator<( const AlfrescoFileInfo& finfo) { return true; return false; } + +/** + * Default constructor + */ +AlfrescoActionInfo::AlfrescoActionInfo(void) { + m_attributes = 0; + m_clientPreActions = 0; +} + +/** + * Class constructor + * + * @param name const String& + * @param attr const unsigned int + * @param preActions const unsigned int + */ +AlfrescoActionInfo::AlfrescoActionInfo( const String& name, const unsigned int attr, const unsigned int preActions) { + m_name = name; + m_attributes = attr; + m_clientPreActions = preActions; +} + +/** + * Return the action information as a string + * + * @return const String + */ +const String AlfrescoActionInfo::toString(void) const { + String str = L"["; + + str.append(getName()); + str.append(L":"); + str.append(getPseudoName()); + str.append(L":Attr=0x"); + str.append(Integer::toHexString(getAttributes())); + str.append(L":preActions=0x"); + str.append(Integer::toHexString(getPreProcessActions())); + + if ( hasConfirmationMessage()) { + str.append(L":Conf="); + str.append(getConfirmationMessage()); + } + str.append(L"]"); + + return str; +} + +/** + * Assignment operator + * + * @param actionInfo const AlfrescoActionInfo& + * @return AlfrescoActionInfo& + */ +AlfrescoActionInfo& AlfrescoActionInfo::operator=( const AlfrescoActionInfo& actionInfo) { + setName(actionInfo.getName()); + setPseudoName(actionInfo.getPseudoName()); + + setAttributes(actionInfo.getAttributes()); + setPreProcessActions(actionInfo.getPreProcessActions()); + + setConfirmationMessage(actionInfo.getConfirmationMessage()); + + return *this; +} diff --git a/source/cpp/CAlfrescoApp/source/alfresco/Desktop.cpp b/source/cpp/CAlfrescoApp/source/alfresco/Desktop.cpp new file mode 100755 index 0000000000..2b185cd905 --- /dev/null +++ b/source/cpp/CAlfrescoApp/source/alfresco/Desktop.cpp @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2005-2006 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ + +#include "alfresco\Desktop.hpp" +#include "util\Exception.h" +#include "util\Integer.h" + +using namespace Alfresco; +using namespace std; + +// Define exceptions + +EXCEPTION_CLASS(Alfresco, DesktopActionException); + +/** + * Class constructor + * + * @param typ Target type + * @param path Target path/id + */ +DesktopTarget::DesktopTarget(int typ, String& path) { + + // Set the target type and path + + m_type = typ; + m_target = path; +} + +/** + * Return the target type as a string + * + * @return const String + */ +const String DesktopTarget::getTypeAsString() const { + + String typStr; + + switch ( isType()) { + case TargetFile: + typStr = L"File"; + break; + case TargetFolder: + typStr = L"Folder"; + break; + case TargetCopiedFile: + typStr = L"File Copy"; + break; + case TargetCopiedFolder: + typStr = L"Folder Copy"; + break; + case TargetNodeRef: + typStr = L"NodeRef"; + break; + } + + return typStr; +} + +/** + * Return the target details as a string + * + * @return const String + */ +const String DesktopTarget::toString( void) const { + + String str = L"["; + + str.append(getTypeAsString()); + str.append(L":"); + str.append(getTarget()); + str.append(L"]"); + + return str; +} + +/** + * Equality operator + * + * @param target const DekstopTarget& + * @return bool + */ +bool DesktopTarget::operator==( const DesktopTarget& target) { + if ( isType() == target.isType() && + getTarget().equals(target.getTarget())) + return true; + return false; +} + +/** + * Less than operator + * + * @param target const DesktopTarget& + * @return bool + */ +bool DesktopTarget::operator<( const DesktopTarget& target) { + if ( isType() == target.isType()) + return getTarget() < target.getTarget(); + else + return isType() < target.isType(); +} + +/** + * Return the required desktop target + * + * @param idx const unsigned int + * @return const DesktopTarget* + */ +const DesktopTarget* DesktopParams::getTarget(const unsigned int idx) const { + + // Range check the index + + if ( idx > m_list.size()) + return NULL; + + // Return the required target + + return m_list[idx]; +} + +/** + * Return the desktop parameters as a string + * + * @return const String + */ +const String DesktopParams::toString(void) const { + + String str = L"["; + + str.append(L"Targets="); + str.append((unsigned int)numberOfTargets()); + str.append(L"]"); + + return str; +} + +/** + * Class constructor + * + * @param sts const unsigned int + * @param msg const wchar_t* + */ +DesktopResponse::DesktopResponse(const unsigned int sts, const wchar_t* msg) { + m_status = sts; + if ( msg != NULL) + m_statusMsg = msg; +} + +/** + * Assignment operator + * + * @param response const DesktopResponse& + * @return DesktopResponse& + */ +DesktopResponse& DesktopResponse::operator=( const DesktopResponse& response) { + m_status = response.getStatus(); + m_statusMsg = response.getStatusMessage(); + + return *this; +} \ No newline at end of file diff --git a/source/cpp/CAlfrescoApp/source/util/Integer.cpp b/source/cpp/CAlfrescoApp/source/util/Integer.cpp index 4013f6f57d..9259296a1d 100644 --- a/source/cpp/CAlfrescoApp/source/util/Integer.cpp +++ b/source/cpp/CAlfrescoApp/source/util/Integer.cpp @@ -27,7 +27,7 @@ using namespace Alfresco; */ String Integer::toHexString( const unsigned int ival) { char buf[32]; - itoa(ival, buf, 16); + _itoa(ival, buf, 16); return String(buf); } @@ -52,7 +52,7 @@ String Integer::toHexString( BUFPTR ptr) { */ String Integer::toString( unsigned int ival, unsigned int radix) { char buf[32]; - itoa(ival, buf, radix); + _itoa(ival, buf, radix); return String(buf); } diff --git a/source/cpp/CAlfrescoApp/source/util/String.cpp b/source/cpp/CAlfrescoApp/source/util/String.cpp index 148cdcb65d..27ddbe069d 100644 --- a/source/cpp/CAlfrescoApp/source/util/String.cpp +++ b/source/cpp/CAlfrescoApp/source/util/String.cpp @@ -695,7 +695,7 @@ void String::append (const String& str) { */ void String::append (const unsigned int ival) { wchar_t buf[32]; - swprintf( buf, L"%u", ival); + swprintf( buf, 32, L"%u", ival); m_string += buf; } @@ -707,7 +707,7 @@ void String::append (const unsigned int ival) { */ void String::append (const unsigned long lval) { wchar_t buf[32]; - swprintf( buf, L"%lu", lval); + swprintf( buf, 32, L"%lu", lval); m_string += buf; } @@ -719,7 +719,7 @@ void String::append (const unsigned long lval) { */ void String::append (const LONG64 l64val) { wchar_t buf[32]; - swprintf( buf, L"%I64u", l64val); + swprintf( buf, 32, L"%I64u", l64val); m_string += buf; } diff --git a/source/java/org/alfresco/filesys/ftp/FTPDataSession.java b/source/java/org/alfresco/filesys/ftp/FTPDataSession.java index 407b5cbc0f..426ab141f8 100644 --- a/source/java/org/alfresco/filesys/ftp/FTPDataSession.java +++ b/source/java/org/alfresco/filesys/ftp/FTPDataSession.java @@ -568,6 +568,22 @@ public class FTPDataSession extends SrvSession implements Runnable netFile = disk.openFile( this, tree, params); } + // Commit any current transaction + + try + { + // Commit or rollback the transaction + + endTransaction(); + } + catch ( Exception ex) + { + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Error committing transaction", ex); + } + // Check if the file has been opened if (netFile == null) @@ -776,6 +792,22 @@ public class FTPDataSession extends SrvSession implements Runnable netFile = disk.createFile(this, tree, params); } + // Commit any current transaction + + try + { + // Commit or rollback the transaction + + endTransaction(); + } + catch ( Exception ex) + { + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Error committing transaction", ex); + } + // Notify change listeners that a new file has been created DiskDeviceContext diskCtx = (DiskDeviceContext) tree.getContext(); diff --git a/source/java/org/alfresco/filesys/ftp/FTPNetworkServer.java b/source/java/org/alfresco/filesys/ftp/FTPNetworkServer.java index a185206f88..d13bd4bb71 100644 --- a/source/java/org/alfresco/filesys/ftp/FTPNetworkServer.java +++ b/source/java/org/alfresco/filesys/ftp/FTPNetworkServer.java @@ -50,10 +50,6 @@ public class FTPNetworkServer extends NetworkFileServer implements Runnable // Session Thread group private static final ThreadGroup THREAD_GROUP_SESSION = new ThreadGroup("FTP_SESSION_GROUP"); - // Server version - - private static final String ServerVersion = "3.5.0"; - // Listen backlog for the server socket protected static final int LISTEN_BACKLOG = 10; @@ -100,10 +96,6 @@ public class FTPNetworkServer extends NetworkFileServer implements Runnable { super("FTP", config); - // Set the server version - - setVersion(ServerVersion); - // Allocate the session lists m_sessions = new FTPSessionList(); @@ -365,7 +357,6 @@ public class FTPNetworkServer extends NetworkFileServer implements Runnable if (logger.isDebugEnabled() && hasDebug()) { logger.debug("FTP Server starting on port " + getPort()); - logger.debug("Version " + isVersion()); } // Create a server socket to listen for incoming FTP session requests diff --git a/source/java/org/alfresco/filesys/ftp/FTPSrvSession.java b/source/java/org/alfresco/filesys/ftp/FTPSrvSession.java index 0b22b83443..9b59e7db2d 100644 --- a/source/java/org/alfresco/filesys/ftp/FTPSrvSession.java +++ b/source/java/org/alfresco/filesys/ftp/FTPSrvSession.java @@ -137,7 +137,7 @@ public class FTPSrvSession extends SrvSession implements Runnable // Flag to control whether data transfers use a seperate thread - private static boolean UseThreadedDataTransfer = true; + private static boolean UseThreadedDataTransfer = false; // Session socket @@ -1839,7 +1839,23 @@ public class FTPSrvSession extends SrvSession implements Runnable netFile = disk.openFile(this, tree, params); } - + + // Commit any current transaction + + try + { + // Commit or rollback the transaction + + endTransaction(); + } + catch ( Exception ex) + { + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Error committing transaction", ex); + } + // Check if the file has been opened if (netFile == null) @@ -1898,7 +1914,7 @@ public class FTPSrvSession extends SrvSession implements Runnable m_dataSess = null; // Close the network file - + disk.closeFile(this, tree, netFile); netFile = null; @@ -2078,6 +2094,22 @@ public class FTPSrvSession extends SrvSession implements Runnable netFile = disk.createFile(this, tree, params); } + // Commit any current transaction + + try + { + // Commit or rollback the transaction + + endTransaction(); + } + catch ( Exception ex) + { + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Error committing transaction", ex); + } + // Notify change listeners that a new file has been created DiskDeviceContext diskCtx = (DiskDeviceContext) tree.getContext(); diff --git a/source/java/org/alfresco/filesys/netbios/server/NetBIOSNameServer.java b/source/java/org/alfresco/filesys/netbios/server/NetBIOSNameServer.java index 5ef7cf59b9..cb08af4a69 100644 --- a/source/java/org/alfresco/filesys/netbios/server/NetBIOSNameServer.java +++ b/source/java/org/alfresco/filesys/netbios/server/NetBIOSNameServer.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; +import java.net.NetworkInterface; import java.net.SocketException; import java.util.Enumeration; import java.util.Hashtable; @@ -42,10 +43,6 @@ public class NetBIOSNameServer extends NetworkServer implements Runnable { private static final Log logger = LogFactory.getLog("org.alfresco.smb.protocol.netbios"); - // Server version - - private static final String ServerVersion = "3.5.0"; - // Various NetBIOS packet sizes public static final int AddNameSize = 256; @@ -412,7 +409,7 @@ public class NetBIOSNameServer extends NetworkServer implements Runnable // Allocate the datagram packet, using the add name buffer - DatagramPacket pkt = new DatagramPacket(buf, len, dest, getPort()); + DatagramPacket pkt = new DatagramPacket(buf, len, dest, RFCNetBIOSProtocol.NAME_PORT); // Send the add name request @@ -469,7 +466,7 @@ public class NetBIOSNameServer extends NetworkServer implements Runnable // Allocate the datagram packet, using the refresh name buffer - DatagramPacket pkt = new DatagramPacket(buf, len, dest, getPort()); + DatagramPacket pkt = new DatagramPacket(buf, len, dest, RFCNetBIOSProtocol.NAME_PORT); // Send the refresh name request @@ -525,7 +522,7 @@ public class NetBIOSNameServer extends NetworkServer implements Runnable // Allocate the datagram packet, using the add name buffer - DatagramPacket pkt = new DatagramPacket(buf, len, dest, getPort()); + DatagramPacket pkt = new DatagramPacket(buf, len, dest, RFCNetBIOSProtocol.NAME_PORT); // Send the add name request @@ -683,7 +680,12 @@ public class NetBIOSNameServer extends NetworkServer implements Runnable { super("NetBIOS", config); + // Set the NetBIOS name server port + + setServerPort( config.getNetBIOSNamePort()); + // Perform common constructor code + commonConstructor(); } @@ -694,11 +696,6 @@ public class NetBIOSNameServer extends NetworkServer implements Runnable */ private final void commonConstructor() throws SocketException { - - // Set the server version - - setVersion(ServerVersion); - // Allocate the local and remote name tables m_localNames = new Vector(); @@ -712,7 +709,6 @@ public class NetBIOSNameServer extends NetworkServer implements Runnable // Set the local address to bind the server to, and server port setBindAddress(getConfiguration().getNetBIOSBindAddress()); - setServerPort(RFCNetBIOSProtocol.NAME_PORT); // Copy the WINS server addresses, if set @@ -1481,6 +1477,54 @@ public class NetBIOSNameServer extends NetworkServer implements Runnable && addrs[i].getHostAddress().equals("0.0.0.0") == false) ipList.add(addrs[i].getAddress()); } + + // Check if the address list is empty, use the network interface list to get the local IP addresses + + if ( ipList.size() == 0) + { + // Enumerate the network adapter list + + Enumeration niEnum = NetworkInterface.getNetworkInterfaces(); + + if ( niEnum != null) + { + while ( niEnum.hasMoreElements()) + { + // Get the current network interface + + NetworkInterface ni = niEnum.nextElement(); + + // Enumerate the addresses for the network adapter + + Enumeration niAddrs = ni.getInetAddresses(); + if ( niAddrs != null) + { + // Check for any valid addresses + + while ( niAddrs.hasMoreElements()) + { + InetAddress curAddr = niAddrs.nextElement(); + + if ( curAddr.getHostAddress().equals("127.0.0.1") == false && + curAddr.getHostAddress().equals("0.0.0.0") == false) + ipList.add( curAddr.getAddress()); + } + } + } + + // DEBUG + + if ( ipList.size() > 0 && logger.isDebugEnabled()) + logger.debug("Found " + ipList.size() + " addresses using interface list"); + } + } + else + { + // DBEUG + + if ( logger.isDebugEnabled()) + logger.debug("Found " + ipList.size() + " addresses using host name lookup"); + } // Check if any addresses were added to the list @@ -1716,7 +1760,6 @@ public class NetBIOSNameServer extends NetworkServer implements Runnable protected final void sendPacket(NetBIOSPacket nbpkt, int len, InetAddress replyAddr, int replyPort) throws java.io.IOException { - // Allocate the datagram packet, using the add name buffer DatagramPacket pkt = new DatagramPacket(nbpkt.getBuffer(), len, replyAddr, replyPort); diff --git a/source/java/org/alfresco/filesys/server/auth/AlfrescoAuthenticator.java b/source/java/org/alfresco/filesys/server/auth/AlfrescoAuthenticator.java index ec20bd579e..817e7b361b 100644 --- a/source/java/org/alfresco/filesys/server/auth/AlfrescoAuthenticator.java +++ b/source/java/org/alfresco/filesys/server/auth/AlfrescoAuthenticator.java @@ -91,7 +91,7 @@ public class AlfrescoAuthenticator extends CifsAuthenticator if ( client.isGuest()) m_authComponent.setGuestUserAsCurrentUser(); else - m_authComponent.setCurrentUser(client.getUserName()); + m_authComponent.setCurrentUser(mapUserNameToPerson(client.getUserName())); // Debug @@ -229,7 +229,7 @@ public class AlfrescoAuthenticator extends CifsAuthenticator // Set the current user to be authenticated, save the authentication token - client.setAuthenticationToken( m_authComponent.setCurrentUser(client.getUserName())); + client.setAuthenticationToken( m_authComponent.setCurrentUser(mapUserNameToPerson(client.getUserName()))); // Get the users home folder node, if available diff --git a/source/java/org/alfresco/filesys/server/auth/CifsAuthenticator.java b/source/java/org/alfresco/filesys/server/auth/CifsAuthenticator.java index d21b62e627..7815a7cf8f 100644 --- a/source/java/org/alfresco/filesys/server/auth/CifsAuthenticator.java +++ b/source/java/org/alfresco/filesys/server/auth/CifsAuthenticator.java @@ -890,4 +890,50 @@ public abstract class CifsAuthenticator } } + /** + * Map the case insensitive logon name to the internal person object user name + * + * @param userName String + * @return String + */ + protected final String mapUserNameToPerson(String userName) + { + // Get the home folder for the user + + UserTransaction tx = m_transactionService.getUserTransaction(); + String personName = null; + + try + { + tx.begin(); + personName = m_personService.getUserIdentifier( userName); + tx.commit(); + } + catch (Throwable ex) + { + try + { + tx.rollback(); + } + catch (Throwable ex2) + { + logger.error("Failed to rollback transaction", ex2); + } + + // Re-throw the exception + + if (ex instanceof RuntimeException) + { + throw (RuntimeException) ex; + } + else + { + throw new RuntimeException("Error during execution of transaction.", ex); + } + } + + // Return the person name + + return personName; + } } \ No newline at end of file diff --git a/source/java/org/alfresco/filesys/server/auth/EnterpriseCifsAuthenticator.java b/source/java/org/alfresco/filesys/server/auth/EnterpriseCifsAuthenticator.java index 3bf487c3ff..0b973efc20 100644 --- a/source/java/org/alfresco/filesys/server/auth/EnterpriseCifsAuthenticator.java +++ b/source/java/org/alfresco/filesys/server/auth/EnterpriseCifsAuthenticator.java @@ -1106,7 +1106,7 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticator implements Ca // Setup the Acegi authenticated user - m_authComponent.setCurrentUser( krbDetails.getUserName()); + m_authComponent.setCurrentUser( mapUserNameToPerson(krbDetails.getUserName())); // Store the full user name in the client information, indicate that this is not a guest logon @@ -1236,7 +1236,7 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticator implements Ca // Setup the Acegi authenticated user - m_authComponent.setCurrentUser( userName); + m_authComponent.setCurrentUser( mapUserNameToPerson(userName)); // Store the full user name in the client information, indicate that this is not a guest logon @@ -1368,7 +1368,7 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticator implements Ca // Setup the Acegi authenticated user - m_authComponent.setCurrentUser( client.getUserName()); + m_authComponent.setCurrentUser( mapUserNameToPerson( client.getUserName())); // Store the full user name in the client information, indicate that this is not a guest logon @@ -1479,7 +1479,7 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticator implements Ca // Setup the Acegi authenticated user - m_authComponent.setCurrentUser( userName); + m_authComponent.setCurrentUser( mapUserNameToPerson( userName)); // Store the full user name in the client information, indicate that this is not a guest logon @@ -1602,7 +1602,7 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticator implements Ca // Setup the Acegi authenticated user - m_authComponent.setCurrentUser( client.getUserName()); + m_authComponent.setCurrentUser( mapUserNameToPerson( client.getUserName())); // Store the full user name in the client information, indicate that this is not a guest logon @@ -1765,7 +1765,7 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticator implements Ca // Setup the Acegi authenticated user - m_authComponent.setCurrentUser( userName); + m_authComponent.setCurrentUser( mapUserNameToPerson( userName)); // Store the full user name in the client information, indicate that this is not a guest logon diff --git a/source/java/org/alfresco/filesys/server/auth/ntlm/AlfrescoAuthenticator.java b/source/java/org/alfresco/filesys/server/auth/ntlm/AlfrescoAuthenticator.java index 9a65f8fea6..5b77d4171d 100644 --- a/source/java/org/alfresco/filesys/server/auth/ntlm/AlfrescoAuthenticator.java +++ b/source/java/org/alfresco/filesys/server/auth/ntlm/AlfrescoAuthenticator.java @@ -20,6 +20,7 @@ import java.security.NoSuchAlgorithmException; import net.sf.acegisecurity.Authentication; import org.alfresco.filesys.server.SrvSession; +import org.alfresco.filesys.server.auth.AuthContext; import org.alfresco.filesys.server.auth.CifsAuthenticator; import org.alfresco.filesys.server.auth.ClientInfo; import org.alfresco.filesys.server.auth.NTLanManAuthContext; @@ -182,26 +183,22 @@ public class AlfrescoAuthenticator extends CifsAuthenticator } /** - * Generate a challenge key + * Return an authentication context for the new session * - * @param sess SrvSession - * @return byte[] + * @return AuthContext */ - public byte[] getChallengeKey(SrvSession sess) + public AuthContext getAuthContext( SMBSrvSession sess) { - // In MD4 mode we generate the challenge locally - - byte[] key = null; - // Check if the client is already authenticated, and it is not a null logon - + + AuthContext authCtx = null; + if ( sess.hasAuthenticationContext() && sess.hasAuthenticationToken() && sess.getClientInformation().getLogonType() != ClientInfo.LogonNull) { // Return the previous challenge, user is already authenticated - NTLanManAuthContext authCtx = (NTLanManAuthContext) sess.getAuthenticationContext(); - key = authCtx.getChallenge(); + authCtx = (NTLanManAuthContext) sess.getAuthenticationContext(); // DEBUG @@ -210,11 +207,10 @@ public class AlfrescoAuthenticator extends CifsAuthenticator } else if ( m_authComponent.getNTLMMode() == NTLMMode.MD4_PROVIDER) { - // Generate a new challenge key, pack the key and return - - key = new byte[8]; - - DataPacker.putIntelLong(m_random.nextLong(), key, 0); + // Create a new authentication context for the session + + authCtx = new NTLanManAuthContext(); + sess.setAuthenticationContext( authCtx); } else { @@ -233,14 +229,17 @@ public class AlfrescoAuthenticator extends CifsAuthenticator // Get the challenge from the token if ( authToken.getChallenge() != null) - key = authToken.getChallenge().getBytes(); + { + authCtx = new NTLanManAuthContext( authToken.getChallenge().getBytes()); + sess.setAuthenticationContext( authCtx); + } } - // Return the challenge + // Return the authentication context - return key; + return authCtx; } - + /** * Perform MD4 user authentication * diff --git a/source/java/org/alfresco/filesys/server/auth/passthru/PassthruAuthenticator.java b/source/java/org/alfresco/filesys/server/auth/passthru/PassthruAuthenticator.java index 634c464b77..7d539310e9 100644 --- a/source/java/org/alfresco/filesys/server/auth/passthru/PassthruAuthenticator.java +++ b/source/java/org/alfresco/filesys/server/auth/passthru/PassthruAuthenticator.java @@ -125,7 +125,7 @@ public class PassthruAuthenticator extends CifsAuthenticator implements SessionL { // Use the existing authentication token - m_authComponent.setCurrentUser(client.getUserName()); + m_authComponent.setCurrentUser( mapUserNameToPerson( client.getUserName())); // Debug @@ -220,42 +220,28 @@ public class PassthruAuthenticator extends CifsAuthenticator implements SessionL // Map the passthru username to an Alfresco person String username = client.getUserName(); - NodeRef userNode = m_personService.getPerson( username); + String personName = m_personService.getUserIdentifier( username); - if ( userNode != null) + if ( personName != null) { - // Get the person name and use that as the current user to line up with permission checks - - String personName = (String) m_nodeService.getProperty(userNode, ContentModel.PROP_USERNAME); + // Use the person name as the current user + m_authComponent.setCurrentUser(personName); // DEBUG if ( logger.isDebugEnabled()) logger.debug("Setting current user using person " + personName + " (username " + username + ")"); + + // Allow the user full access to the server + + authSts = CifsAuthenticator.AUTH_ALLOW; + + // Debug + + if (logger.isDebugEnabled()) + logger.debug("Passthru authenticate user=" + client.getUserName() + ", FULL"); } - else - { - // Set using the user name, lowercase the name if the person service is case insensitive - - if ( m_personService.getUserNamesAreCaseSensitive() == false) - username = username.toLowerCase(); - m_authComponent.setCurrentUser( username); - - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug("Setting current user using username " + username); - } - - // Allow the user full access to the server - - authSts = CifsAuthenticator.AUTH_ALLOW; - - // Debug - - if (logger.isDebugEnabled()) - logger.debug("Passthru authenticate user=" + client.getUserName() + ", FULL"); } finally { diff --git a/source/java/org/alfresco/filesys/server/config/ServerConfiguration.java b/source/java/org/alfresco/filesys/server/config/ServerConfiguration.java index d11781753a..6895b50b97 100644 --- a/source/java/org/alfresco/filesys/server/config/ServerConfiguration.java +++ b/source/java/org/alfresco/filesys/server/config/ServerConfiguration.java @@ -19,7 +19,9 @@ package org.alfresco.filesys.server.config; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; +import java.net.NetworkInterface; import java.net.Socket; +import java.net.SocketException; import java.net.UnknownHostException; import java.security.Provider; import java.security.Security; @@ -62,11 +64,15 @@ import org.alfresco.filesys.server.core.ShareType; import org.alfresco.filesys.server.core.SharedDevice; import org.alfresco.filesys.server.core.SharedDeviceList; import org.alfresco.filesys.server.filesys.DefaultShareMapper; -import org.alfresco.filesys.server.filesys.DiskDeviceContext; import org.alfresco.filesys.server.filesys.DiskInterface; import org.alfresco.filesys.server.filesys.DiskSharedDevice; import org.alfresco.filesys.server.filesys.HomeShareMapper; import org.alfresco.filesys.smb.ServerType; +import org.alfresco.filesys.smb.TcpipSMB; +import org.alfresco.filesys.smb.server.repo.ContentContext; +import org.alfresco.filesys.smb.server.repo.DesktopAction; +import org.alfresco.filesys.smb.server.repo.DesktopActionException; +import org.alfresco.filesys.smb.server.repo.DesktopActionTable; import org.alfresco.filesys.util.IPAddress; import org.alfresco.filesys.util.X64; import org.alfresco.repo.security.authentication.AuthenticationComponent; @@ -182,6 +188,16 @@ public class ServerConfiguration implements ApplicationListener private String m_broadcast; + // NetBIOS ports + + private int m_nbNamePort = RFCNetBIOSProtocol.NAME_PORT; + private int m_nbSessPort = RFCNetBIOSProtocol.PORT; + private int m_nbDatagramPort = RFCNetBIOSProtocol.DATAGRAM; + + // Native SMB port + + private int m_tcpSMBPort = TcpipSMB.PORT; + // Announce the server to network neighborhood, announcement interval in // minutes @@ -586,7 +602,7 @@ public class ServerConfiguration implements ApplicationListener m_platform = PlatformType.LINUX; else if (osName.startsWith("Mac OS X")) m_platform = PlatformType.MACOSX; - else if (osName.startsWith("Solaris")) + else if (osName.startsWith("Solaris") || osName.startsWith("SunOS")) m_platform = PlatformType.SOLARIS; } @@ -800,103 +816,198 @@ public class ServerConfiguration implements ApplicationListener platformOK = true; } - // Check if the broadcast mask has been specified - - if (getBroadcastMask() == null) - throw new AlfrescoRuntimeException("Network broadcast mask not specified"); - // Enable the NetBIOS SMB support, if enabled for this platform setNetBIOSSMB(platformOK); - // Check for a bind address - - String bindto = elem.getAttribute("bindto"); - if (bindto != null && bindto.length() > 0) + // Parse/check NetBIOS settings, if enabled + + if ( hasNetBIOSSMB()) { - - // Validate the bind address - - try - { - - // Check the bind address - - InetAddress bindAddr = InetAddress.getByName(bindto); - - // Set the bind address for the NetBIOS name server - - setNetBIOSBindAddress(bindAddr); - } - catch (UnknownHostException ex) - { - throw new AlfrescoRuntimeException("Invalid NetBIOS bind address"); - } - } - else if (hasSMBBindAddress()) - { - - // Use the SMB bind address for the NetBIOS name server - - setNetBIOSBindAddress(getSMBBindAddress()); - } - else - { - // Get a list of all the local addresses - - InetAddress[] addrs = null; - - try - { - // Get the local server IP address list - - addrs = InetAddress.getAllByName(InetAddress.getLocalHost().getHostName()); - } - catch (UnknownHostException ex) - { - logger.error("Failed to get local address list", ex); - } - - // Check the address list for one or more valid local addresses filtering out the loopback address - - int addrCnt = 0; - - if ( addrs != null) - { - for (int i = 0; i < addrs.length; i++) - { - - // Check for a valid address, filter out '127.0.0.1' and '0.0.0.0' addresses - - if (addrs[i].getHostAddress().equals("127.0.0.1") == false - && addrs[i].getHostAddress().equals("0.0.0.0") == false) - addrCnt++; - } - } - - // Check if any addresses were found - - if ( addrCnt == 0) - { - // Log the available IP addresses - - if ( logger.isDebugEnabled()) - { - logger.debug("Local address list dump :-"); - if ( addrs != null) - { - for ( int i = 0; i < addrs.length; i++) - logger.debug( " Address: " + addrs[i]); - } - else - logger.debug(" No addresses"); - } - - // Throw an exception to stop the CIFS/NetBIOS name server from starting - - throw new AlfrescoRuntimeException( "Failed to get IP address(es) for the local server, check hosts file and/or DNS setup"); - } - + // Check if the broadcast mask has been specified + + if (getBroadcastMask() == null) + throw new AlfrescoRuntimeException("Network broadcast mask not specified"); + + // Check for a bind address + + String bindto = elem.getAttribute("bindto"); + if (bindto != null && bindto.length() > 0) + { + + // Validate the bind address + + try + { + + // Check the bind address + + InetAddress bindAddr = InetAddress.getByName(bindto); + + // Set the bind address for the NetBIOS name server + + setNetBIOSBindAddress(bindAddr); + } + catch (UnknownHostException ex) + { + throw new AlfrescoRuntimeException("Invalid NetBIOS bind address"); + } + } + else if (hasSMBBindAddress()) + { + + // Use the SMB bind address for the NetBIOS name server + + setNetBIOSBindAddress(getSMBBindAddress()); + } + else + { + // Get a list of all the local addresses + + InetAddress[] addrs = null; + + try + { + // Get the local server IP address list + + addrs = InetAddress.getAllByName(InetAddress.getLocalHost().getHostName()); + } + catch (UnknownHostException ex) + { + logger.error("Failed to get local address list", ex); + } + + // Check the address list for one or more valid local addresses filtering out the loopback address + + int addrCnt = 0; + + if ( addrs != null) + { + for (int i = 0; i < addrs.length; i++) + { + + // Check for a valid address, filter out '127.0.0.1' and '0.0.0.0' addresses + + if (addrs[i].getHostAddress().equals("127.0.0.1") == false + && addrs[i].getHostAddress().equals("0.0.0.0") == false) + addrCnt++; + } + } + + // Check if any addresses were found + + if ( addrCnt == 0) + { + // Enumerate the network adapter list + + Enumeration niEnum = null; + + try + { + niEnum = NetworkInterface.getNetworkInterfaces(); + } + catch (SocketException ex) + { + } + + if ( niEnum != null) + { + while ( niEnum.hasMoreElements()) + { + // Get the current network interface + + NetworkInterface ni = niEnum.nextElement(); + + // Enumerate the addresses for the network adapter + + Enumeration niAddrs = ni.getInetAddresses(); + if ( niAddrs != null) + { + // Check for any valid addresses + + while ( niAddrs.hasMoreElements()) + { + InetAddress curAddr = niAddrs.nextElement(); + + if ( curAddr.getHostAddress().equals("127.0.0.1") == false && + curAddr.getHostAddress().equals("0.0.0.0") == false) + addrCnt++; + } + } + } + + // DEBUG + + if ( addrCnt > 0 && logger.isDebugEnabled()) + logger.debug("Found valid IP address from interface list"); + } + + // Check if we found any valid network addresses + + if ( addrCnt == 0) + { + // Log the available IP addresses + + if ( logger.isDebugEnabled()) + { + logger.debug("Local address list dump :-"); + if ( addrs != null) + { + for ( int i = 0; i < addrs.length; i++) + logger.debug( " Address: " + addrs[i]); + } + else + logger.debug(" No addresses"); + } + + // Throw an exception to stop the CIFS/NetBIOS name server from starting + + throw new AlfrescoRuntimeException( "Failed to get IP address(es) for the local server, check hosts file and/or DNS setup"); + } + } + } + + // Check if the session port has been specified + + String portNum = elem.getAttribute("sessionPort"); + if ( portNum != null && portNum.length() > 0) { + try { + setNetBIOSSessionPort(Integer.parseInt(portNum)); + if ( getNetBIOSSessionPort() <= 0 || getNetBIOSSessionPort() >= 65535) + throw new AlfrescoRuntimeException("NetBIOS session port out of valid range"); + } + catch (NumberFormatException ex) { + throw new AlfrescoRuntimeException("Invalid NetBIOS session port"); + } + } + + // Check if the name port has been specified + + portNum = elem.getAttribute("namePort"); + if ( portNum != null && portNum.length() > 0) { + try { + setNetBIOSNamePort(Integer.parseInt(portNum)); + if ( getNetBIOSNamePort() <= 0 || getNetBIOSNamePort() >= 65535) + throw new AlfrescoRuntimeException("NetBIOS name port out of valid range"); + } + catch (NumberFormatException ex) { + throw new AlfrescoRuntimeException("Invalid NetBIOS name port"); + } + } + + // Check if the datagram port has been specified + + portNum = elem.getAttribute("datagramPort"); + if ( portNum != null && portNum.length() > 0) { + try { + setNetBIOSDatagramPort(Integer.parseInt(portNum)); + if ( getNetBIOSDatagramPort() <= 0 || getNetBIOSDatagramPort() >= 65535) + throw new AlfrescoRuntimeException("NetBIOS datagram port out of valid range"); + } + catch (NumberFormatException ex) { + throw new AlfrescoRuntimeException("Invalid NetBIOS datagram port"); + } + } } } else @@ -937,6 +1048,20 @@ public class ServerConfiguration implements ApplicationListener // Enable the TCP/IP SMB support, if enabled for this platform setTcpipSMB(platformOK); + + // Check if the port has been specified + + String portNum = elem.getAttribute("port"); + if ( portNum != null && portNum.length() > 0) { + try { + setTcpipSMBPort(Integer.parseInt(portNum)); + if ( getTcpipSMBPort() <= 0 || getTcpipSMBPort() >= 65535) + throw new AlfrescoRuntimeException("TCP/IP SMB port out of valid range"); + } + catch (NumberFormatException ex) { + throw new AlfrescoRuntimeException("Invalid TCP/IP SMB port"); + } + } } else { @@ -1460,7 +1585,7 @@ public class ServerConfiguration implements ApplicationListener } } - // Get the top level filesystems confgiruation element + // Get the top level filesystems configuration element ConfigElement filesystems = config.getConfigElement("filesystems"); @@ -1511,8 +1636,9 @@ public class ServerConfiguration implements ApplicationListener { // Create a new filesystem driver instance and create a context for // the new filesystem + DiskInterface filesysDriver = this.diskInterface; - DiskDeviceContext filesysContext = (DiskDeviceContext) filesysDriver.createContext(elem); + ContentContext filesysContext = (ContentContext) filesysDriver.createContext(elem); // Check if an access control list has been specified @@ -1542,6 +1668,18 @@ public class ServerConfiguration implements ApplicationListener DiskSharedDevice filesys = new DiskSharedDevice(filesysName, filesysDriver, filesysContext); + // Attach desktop actions to the filesystem + + ConfigElement deskActionsElem = elem.getChild("desktopActions"); + if ( deskActionsElem != null) + { + // Get the desktop actions list + + DesktopActionTable desktopActions = processDesktopActions(deskActionsElem, filesys); + if ( desktopActions != null) + filesysContext.setDesktopActions( desktopActions, filesysDriver); + } + // Add any access controls to the share filesys.setAccessControlList(acls); @@ -1682,6 +1820,8 @@ public class ServerConfiguration implements ApplicationListener setAuthenticator(auth, authElem, allowGuest); auth.setMapToGuest( mapGuest); } + else + throw new AlfrescoRuntimeException("Authenticator not specified"); } /** @@ -1775,6 +1915,99 @@ public class ServerConfiguration implements ApplicationListener return acls; } + /** + * Process a desktop actions sub-section and return the desktop action table + * + * @param deskActionElem ConfigElement + * @param fileSys DiskSharedDevice + */ + private final DesktopActionTable processDesktopActions(ConfigElement deskActionElem, DiskSharedDevice fileSys) + { + // Get the desktop action configuration elements + + DesktopActionTable desktopActions = null; + List actionElems = deskActionElem.getChildren(); + + if ( actionElems != null) + { + // Check for the global configuration section + + ConfigElement globalConfig = deskActionElem.getChild("global"); + + // Allocate the actions table + + desktopActions = new DesktopActionTable(); + + // Process the desktop actions list + + for ( ConfigElement actionElem : actionElems) + { + if ( actionElem.getName().equals("action")) + { + // Get the desktop action class name or bean id + + ConfigElement className = actionElem.getChild("class"); + if ( className != null) + { + // Load the desktop action class, create a new instance + + Object actionObj = null; + + try + { + // Create a new desktop action instance + + actionObj = Class.forName(className.getValue()).newInstance(); + + // Make sure the object is a desktop action + + if ( actionObj instanceof DesktopAction) + { + // Initialize the desktop action + + DesktopAction deskAction = (DesktopAction) actionObj; + deskAction.initializeAction(globalConfig, actionElem, fileSys); + + // Add the action to the list of desktop actions + + desktopActions.addAction(deskAction); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("Added desktop action " + deskAction.getName()); + } + else + throw new AlfrescoRuntimeException("Desktop action does not extend DesktopAction class, " + className.getValue()); + } + catch ( ClassNotFoundException ex) + { + throw new AlfrescoRuntimeException("Desktop action class not found, " + className.getValue()); + } + catch (IllegalAccessException ex) + { + throw new AlfrescoRuntimeException("Failed to create desktop action instance, " + className.getValue(), ex); + } + catch ( InstantiationException ex) + { + throw new AlfrescoRuntimeException("Failed to create desktop action instance, " + className.getValue(), ex); + } + catch (DesktopActionException ex) + { + throw new AlfrescoRuntimeException("Failed to initialize desktop action", ex); + } + } + } + else if ( actionElem.getName().equals("global") == false) + throw new AlfrescoRuntimeException("Invalid configuration element in desktopActions section, " + actionElem.getName()); + } + } + + // Return the desktop actions list + + return desktopActions; + } + /** * Parse the platforms attribute returning the set of platform ids * @@ -2013,6 +2246,36 @@ public class ServerConfiguration implements ApplicationListener return m_nbBindAddress; } + /** + * Return the NetBIOS name server port + * + * @return int + */ + public final int getNetBIOSNamePort() + { + return m_nbNamePort; + } + + /** + * Return the NetBIOS session port + * + * @return int + */ + public final int getNetBIOSSessionPort() + { + return m_nbSessPort; + } + + /** + * Return the NetBIOS datagram port + * + * @return int + */ + public final int getNetBIOSDatagramPort() + { + return m_nbDatagramPort; + } + /** * Return the network broadcast mask to be used for broadcast datagrams. * @@ -2154,6 +2417,16 @@ public class ServerConfiguration implements ApplicationListener return m_win32NBUseWinsock; } + /** + * Return the native SMB port + * + * @return int + */ + public final int getTcpipSMBPort() + { + return m_tcpSMBPort; + } + /** * Return the timezone name * @@ -2716,6 +2989,36 @@ public class ServerConfiguration implements ApplicationListener m_netBIOSEnable = ena; } + /** + * Set the NetBIOS name server port + * + * @param port int + */ + public final void setNetBIOSNamePort(int port) + { + m_nbNamePort = port; + } + + /** + * Set the NetBIOS session port + * + * @param port int + */ + public final void setNetBIOSSessionPort(int port) + { + m_nbSessPort = port; + } + + /** + * Set the NetBIOS datagram port + * + * @param port int + */ + public final void setNetBIOSDatagramPort(int port) + { + m_nbDatagramPort = port; + } + /** * Enable/disable the TCP/IP SMB support * @@ -2726,6 +3029,16 @@ public class ServerConfiguration implements ApplicationListener m_tcpSMBEnable = ena; } + /** + * Set the TCP/IP SMB port + * + * @param port int + */ + public final void setTcpipSMBPort( int port) + { + m_tcpSMBPort = port; + } + /** * Enable/disable the Win32 NetBIOS SMB support * diff --git a/source/java/org/alfresco/filesys/server/smb/repo/ContentIOControlHandler.java b/source/java/org/alfresco/filesys/server/smb/repo/ContentIOControlHandler.java deleted file mode 100644 index 47b947331f..0000000000 --- a/source/java/org/alfresco/filesys/server/smb/repo/ContentIOControlHandler.java +++ /dev/null @@ -1,589 +0,0 @@ -/* - * Copyright (C) 2005-2006 Alfresco, Inc. - * - * Licensed under the Mozilla Public License version 1.1 - * with a permitted attribution clause. You may obtain a - * copy of the License at - * - * http://www.alfresco.org/legal/license.txt - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - * either express or implied. See the License for the specific - * language governing permissions and limitations under the - * License. - */ -package org.alfresco.filesys.server.smb.repo; - -import java.io.FileNotFoundException; - -import org.alfresco.filesys.server.SrvSession; -import org.alfresco.filesys.server.filesys.DiskDeviceContext; -import org.alfresco.filesys.server.filesys.FileName; -import org.alfresco.filesys.server.filesys.IOControlNotImplementedException; -import org.alfresco.filesys.server.filesys.NetworkFile; -import org.alfresco.filesys.server.filesys.NotifyChange; -import org.alfresco.filesys.server.filesys.TreeConnection; -import org.alfresco.filesys.smb.NTIOCtl; -import org.alfresco.filesys.smb.SMBException; -import org.alfresco.filesys.smb.SMBStatus; -import org.alfresco.filesys.smb.server.repo.CifsHelper; -import org.alfresco.filesys.smb.server.repo.ContentDiskDriver; -import org.alfresco.filesys.smb.server.repo.IOControlHandler; -import org.alfresco.filesys.util.DataBuffer; -import org.alfresco.model.ContentModel; -import org.alfresco.service.cmr.coci.CheckOutCheckInService; -import org.alfresco.service.cmr.lock.LockType; -import org.alfresco.service.cmr.repository.ContentData; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.transaction.TransactionService; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * Content Disk Driver I/O Control Handler Class - * - *

Provides the custom I/O control code handling used by the CIFS client interface application. - * - * @author gkspencer - */ -public class ContentIOControlHandler implements IOControlHandler -{ - // Logging - - private static final Log logger = LogFactory.getLog(ContentIOControlHandler.class); - - // Services and helpers - - private CifsHelper cifsHelper; - private TransactionService transactionService; - private NodeService nodeService; - private CheckOutCheckInService checkInOutService; - - private ContentDiskDriver contentDriver; - - /** - * Default constructor - */ - public ContentIOControlHandler() - { - } - - /** - * Initalize the I/O control handler - * - * @param contentDriver ContentDiskDriver - * @param cifsHelper CifsHelper - * @param transService TransactionService - * @param nodeService NodeService - * @param cociService CheckOutCheckInService - */ - public void initialize( ContentDiskDriver contentDriver, CifsHelper cifsHelper, - TransactionService transService, NodeService nodeService, CheckOutCheckInService cociService) - { - this.contentDriver = contentDriver; - this.cifsHelper = cifsHelper; - this.transactionService = transService; - this.nodeService = nodeService; - this.checkInOutService = cociService; - } - - /** - * Process a filesystem I/O control request - * - * @param sess Server session - * @param tree Tree connection. - * @param ctrlCode I/O control code - * @param fid File id - * @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 - * @exception IOControlNotImplementedException - * @exception SMBException - */ - public DataBuffer processIOControl(SrvSession sess, TreeConnection tree, int ctrlCode, int fid, DataBuffer dataBuf, - boolean isFSCtrl, int filter) - throws IOControlNotImplementedException, SMBException - { - // Validate the file id - - NetworkFile netFile = tree.findFile(fid); - if ( netFile == null || netFile.isDirectory() == false) - throw new SMBException(SMBStatus.NTErr, SMBStatus.NTInvalidParameter); - - // Split the control code - - int devType = NTIOCtl.getDeviceType(ctrlCode); - int ioFunc = NTIOCtl.getFunctionCode(ctrlCode); - - if ( devType != NTIOCtl.DeviceFileSystem || dataBuf == null) - throw new IOControlNotImplementedException(); - - // 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.getString(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 - - NodeRef folderNode = null; - - try - { - folderNode = contentDriver.getNodeForPath(tree, netFile.getFullName()); - - if ( cifsHelper.isDirectory( folderNode) == false) - folderNode = null; - } - catch ( FileNotFoundException ex) - { - folderNode = null; - } - - // If the folder node is not valid return an error - - if ( folderNode == null) - throw new SMBException(SMBStatus.NTErr, SMBStatus.NTAccessDenied); - - // Debug - - if ( logger.isInfoEnabled()) { - logger.info("IO control func=0x" + Integer.toHexString(ioFunc) + ", fid=" + fid + ", buffer=" + dataBuf); - logger.info(" Folder nodeRef=" + folderNode); - } - - // Check if the I/O control code is one of our custom codes - - DataBuffer retBuffer = null; - - switch ( ioFunc) - { - // Probe to check if this is an Alfresco CIFS server - - case IOControl.CmdProbe: - - // Return a buffer with the signature - - retBuffer = new DataBuffer(IOControl.Signature.length()); - retBuffer.putFixedString(IOControl.Signature, IOControl.Signature.length()); - retBuffer.putInt(IOControl.StsSuccess); - break; - - // Get file information for a file within the current folder - - case IOControl.CmdFileStatus: - - // Process the file status request - - retBuffer = procIOFileStatus( sess, tree, dataBuf, folderNode); - break; - - // Check-in file request - - case IOControl.CmdCheckIn: - - // Process the check-in request - - retBuffer = procIOCheckIn( sess, tree, dataBuf, folderNode, netFile); - break; - - // Check-out file request - - case IOControl.CmdCheckOut: - - // Process the check-out request - - retBuffer = procIOCheckOut( sess, tree, dataBuf, folderNode, netFile); - break; - - // Unknown I/O control code - - default: - throw new IOControlNotImplementedException(); - } - - // Return the reply buffer, may be null - - return retBuffer; - } - - /** - * Process the file status I/O request - * - * @param sess Server session - * @param tree Tree connection - * @param reqBuf Request buffer - * @param folderNode NodeRef of parent folder - * @return DataBuffer - */ - private final DataBuffer procIOFileStatus( SrvSession sess, TreeConnection tree, DataBuffer reqBuf, NodeRef folderNode) - { - // Start a transaction - - sess.beginTransaction( transactionService, true); - - // Get the file name from the request - - String fName = reqBuf.getString( true); - logger.info(" File status, fname=" + fName); - - // Create a response buffer - - DataBuffer respBuf = new DataBuffer(256); - respBuf.putFixedString(IOControl.Signature, IOControl.Signature.length()); - - // Get the node for the file/folder - - NodeRef childNode = null; - - try - { - childNode = cifsHelper.getNodeRef( folderNode, fName); - } - catch (FileNotFoundException ex) - { - } - - // Check if the file/folder was found - - if ( childNode == null) - { - // Return an error response - - respBuf.putInt(IOControl.StsFileNotFound); - return respBuf; - } - - // Check if this is a file or folder node - - if ( cifsHelper.isDirectory( childNode)) - { - // Only return the status and node type for folders - - respBuf.putInt(IOControl.StsSuccess); - respBuf.putInt(IOControl.TypeFolder); - } - else - { - // Indicate that this is a file node - - respBuf.putInt(IOControl.StsSuccess); - respBuf.putInt(IOControl.TypeFile); - - // Check if this file is a working copy - - if ( nodeService.hasAspect( childNode, ContentModel.ASPECT_WORKING_COPY)) - { - // Indicate that this is a working copy - - respBuf.putInt(IOControl.True); - - // Get the owner username and file it was copied from - - String owner = (String) nodeService.getProperty( childNode, ContentModel.PROP_WORKING_COPY_OWNER); - String copiedFrom = null; - - if ( nodeService.hasAspect( childNode, ContentModel.ASPECT_COPIEDFROM)) - { - // Get the path of the file the working copy was generated from - - NodeRef fromNode = (NodeRef) nodeService.getProperty( childNode, ContentModel.PROP_COPY_REFERENCE); - if ( fromNode != null) - copiedFrom = (String) nodeService.getProperty( fromNode, ContentModel.PROP_NAME); - } - - // Pack the owner and copied from values - - respBuf.putString(owner != null ? owner : "", true, true); - respBuf.putString(copiedFrom != null ? copiedFrom : "", true, true); - } - else - { - // Not a working copy - - respBuf.putInt(IOControl.False); - } - - // Check the lock status of the file - - if ( nodeService.hasAspect( childNode, ContentModel.ASPECT_LOCKABLE)) - { - // Get the lock type and owner - - String lockTypeStr = (String) nodeService.getProperty( childNode, ContentModel.PROP_LOCK_TYPE); - String lockOwner = null; - - if ( lockTypeStr != null) - lockOwner = (String) nodeService.getProperty( childNode, ContentModel.PROP_LOCK_OWNER); - - // Pack the lock type, and owner if there is a lock on the file - - if ( lockTypeStr == null) - respBuf.putInt(IOControl.LockNone); - else - { - LockType lockType = LockType.valueOf( lockTypeStr); - - respBuf.putInt(lockType == LockType.READ_ONLY_LOCK ? IOControl.LockRead : IOControl.LockWrite); - respBuf.putString(lockOwner != null ? lockOwner : "", true, true); - } - } - else - { - // File is not lockable - - respBuf.putInt(IOControl.LockNone); - } - - // Get the content data details for the file - - ContentData contentData = (ContentData) nodeService.getProperty( childNode, ContentModel.PROP_CONTENT); - - if ( contentData != null) - { - // Get the content mime-type - - String mimeType = contentData.getMimetype(); - - // Pack the content length and mime-type - - respBuf.putInt( IOControl.True); - respBuf.putLong( contentData.getSize()); - respBuf.putString( mimeType != null ? mimeType : "", true, true); - } - else - { - // File does not have any content - - respBuf.putInt( IOControl.False); - } - } - - // Return the response - - return respBuf; - } - - /** - * Process the check in I/O request - * - * @param sess Server session - * @param tree Tree connection - * @param reqBuf Request buffer - * @param folderNode NodeRef of parent folder - * @param netFile NetworkFile for the folder - * @return DataBuffer - */ - private final DataBuffer procIOCheckIn( SrvSession sess, TreeConnection tree, DataBuffer reqBuf, NodeRef folderNode, - NetworkFile netFile) - { - // Start a transaction - - sess.beginTransaction( transactionService, false); - - // Get the file name from the request - - String fName = reqBuf.getString( true); - boolean keepCheckedOut = reqBuf.getInt() == IOControl.True ? true : false; - - logger.info(" CheckIn, fname=" + fName + ", keepCheckedOut=" + keepCheckedOut); - - // Create a response buffer - - DataBuffer respBuf = new DataBuffer(256); - respBuf.putFixedString(IOControl.Signature, IOControl.Signature.length()); - - // Get the node for the file/folder - - NodeRef childNode = null; - - try - { - childNode = cifsHelper.getNodeRef( folderNode, fName); - } - catch (FileNotFoundException ex) - { - } - - // Check if the file/folder was found - - if ( childNode == null) - { - // Return an error response - - respBuf.putInt(IOControl.StsFileNotFound); - return respBuf; - } - - // Check if this is a file or folder node - - if ( cifsHelper.isDirectory( childNode)) - { - // Return an error status, attempt to check in a folder - - respBuf.putInt(IOControl.StsBadParameter); - } - else - { - // Check if this file is a working copy - - if ( nodeService.hasAspect( childNode, ContentModel.ASPECT_WORKING_COPY)) - { - try - { - // Check in the file - - checkInOutService.checkin( childNode, null, null, keepCheckedOut); - - // Check in was successful - - respBuf.putInt( IOControl.StsSuccess); - - // Check if there are any file/directory change notify requests active - - DiskDeviceContext diskCtx = (DiskDeviceContext) tree.getContext(); - if (diskCtx.hasChangeHandler()) { - - // Build the relative path to the checked in file - - String fileName = FileName.buildPath( netFile.getFullName(), null, fName, FileName.DOS_SEPERATOR); - - // Queue a file deleted change notification - - diskCtx.getChangeHandler().notifyFileChanged(NotifyChange.ActionRemoved, fileName); - } - } - catch (Exception ex) - { - // Return an error status and message - - respBuf.setPosition( IOControl.Signature.length()); - respBuf.putInt(IOControl.StsError); - respBuf.putString( ex.getMessage(), true, true); - } - } - else - { - // Not a working copy - - respBuf.putInt(IOControl.StsNotWorkingCopy); - } - } - - // Return the response - - return respBuf; - } - - /** - * Process the check out I/O request - * - * @param sess Server session - * @param tree Tree connection - * @param reqBuf Request buffer - * @param folderNode NodeRef of parent folder - * @param netFile NetworkFile for the folder - * @return DataBuffer - */ - private final DataBuffer procIOCheckOut( SrvSession sess, TreeConnection tree, DataBuffer reqBuf, NodeRef folderNode, - NetworkFile netFile) - { - // Start a transaction - - sess.beginTransaction( transactionService, false); - - // Get the file name from the request - - String fName = reqBuf.getString( true); - - logger.info(" CheckOut, fname=" + fName); - - // Create a response buffer - - DataBuffer respBuf = new DataBuffer(256); - respBuf.putFixedString(IOControl.Signature, IOControl.Signature.length()); - - // Get the node for the file/folder - - NodeRef childNode = null; - - try - { - childNode = cifsHelper.getNodeRef( folderNode, fName); - } - catch (FileNotFoundException ex) - { - } - - // Check if the file/folder was found - - if ( childNode == null) - { - // Return an error response - - respBuf.putInt(IOControl.StsFileNotFound); - return respBuf; - } - - // Check if this is a file or folder node - - if ( cifsHelper.isDirectory( childNode)) - { - // Return an error status, attempt to check in a folder - - respBuf.putInt(IOControl.StsBadParameter); - } - else - { - try - { - // Check out the file - - NodeRef workingCopyNode = checkInOutService.checkout( childNode); - - // Get the working copy file name - - String workingCopyName = (String) nodeService.getProperty( workingCopyNode, ContentModel.PROP_NAME); - - // Check out was successful, pack the working copy name - - respBuf.putInt( IOControl.StsSuccess); - respBuf.putString( workingCopyName, true, true); - - // Check if there are any file/directory change notify requests active - - DiskDeviceContext diskCtx = (DiskDeviceContext) tree.getContext(); - if (diskCtx.hasChangeHandler()) { - - // Build the relative path to the checked in file - - String fileName = FileName.buildPath( netFile.getFullName(), null, workingCopyName, FileName.DOS_SEPERATOR); - - // Queue a file added change notification - - diskCtx.getChangeHandler().notifyFileChanged(NotifyChange.ActionAdded, fileName); - } - } - catch (Exception ex) - { - // Return an error status and message - - respBuf.setPosition( IOControl.Signature.length()); - respBuf.putInt(IOControl.StsError); - respBuf.putString( ex.getMessage(), true, true); - } - } - - // Return the response - - return respBuf; - } -} diff --git a/source/java/org/alfresco/filesys/server/smb/repo/IOControl.java b/source/java/org/alfresco/filesys/server/smb/repo/IOControl.java deleted file mode 100644 index 7fb9b49f52..0000000000 --- a/source/java/org/alfresco/filesys/server/smb/repo/IOControl.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2005-2006 Alfresco, Inc. - * - * Licensed under the Mozilla Public License version 1.1 - * with a permitted attribution clause. You may obtain a - * copy of the License at - * - * http://www.alfresco.org/legal/license.txt - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - * either express or implied. See the License for the specific - * language governing permissions and limitations under the - * License. - */ -package org.alfresco.filesys.server.smb.repo; - -import org.alfresco.filesys.smb.NTIOCtl; - -/** - * Content Disk Driver I/O Control Codes Class - * - *

contains I/O control codes and status codes used by the content disk driver I/O control - * implementation. - * - * @author gkspencer - */ -public class IOControl -{ - // Custom I/O control codes - - public static final int CmdProbe = NTIOCtl.FsCtlCustom; - public static final int CmdFileStatus = NTIOCtl.FsCtlCustom + 1; - public static final int CmdCheckOut = NTIOCtl.FsCtlCustom + 2; - public static final int CmdCheckIn = NTIOCtl.FsCtlCustom + 3; - - // I/O control request/response signature - - public static final String Signature = "ALFRESCO"; - - // I/O control status codes - - public static final int StsSuccess = 0; - - public static final int StsError = 1; - public static final int StsFileNotFound = 2; - public static final int StsAccessDenied = 3; - public static final int StsBadParameter = 4; - public static final int StsNotWorkingCopy = 5; - - // Boolean field values - - public static final int True = 1; - public static final int False = 0; - - // File status field values - // - // Node type - - public static final int TypeFile = 0; - public static final int TypeFolder = 1; - - // Lock status - - public static final int LockNone = 0; - public static final int LockRead = 1; - public static final int LockWrite = 2; -} diff --git a/source/java/org/alfresco/filesys/smb/mailslot/TcpipNetBIOSHostAnnouncer.java b/source/java/org/alfresco/filesys/smb/mailslot/TcpipNetBIOSHostAnnouncer.java index 7f29cf8075..afa02c99f0 100644 --- a/source/java/org/alfresco/filesys/smb/mailslot/TcpipNetBIOSHostAnnouncer.java +++ b/source/java/org/alfresco/filesys/smb/mailslot/TcpipNetBIOSHostAnnouncer.java @@ -46,7 +46,6 @@ public class TcpipNetBIOSHostAnnouncer extends HostAnnouncer // Broadcast address and port private InetAddress m_bcastAddr; - private int m_bcastPort = RFCNetBIOSProtocol.DATAGRAM; // NetBIOS datagram @@ -140,7 +139,7 @@ public class TcpipNetBIOSHostAnnouncer extends HostAnnouncer public final void setBroadcastAddress(String addr, int port) throws UnknownHostException { m_bcastAddr = InetAddress.getByName(addr); - m_bcastPort = port; + m_port = port; } /** @@ -198,11 +197,15 @@ public class TcpipNetBIOSHostAnnouncer extends HostAnnouncer */ protected void sendAnnouncement(String hostName, byte[] buf, int offset, int len) throws Exception { - + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("Send NetBIOS host announcement to " + m_bcastAddr.getHostAddress() + ", port " + getPort()); + // Send the host announce datagram m_nbdgram.SendDatagram(NetBIOSDatagram.DIRECT_GROUP, hostName, NetBIOSName.FileServer, getDomain(), - NetBIOSName.MasterBrowser, buf, len, offset); + NetBIOSName.MasterBrowser, buf, len, offset, m_bcastAddr, getPort()); } /** diff --git a/source/java/org/alfresco/filesys/smb/server/NetBIOSSessionSocketHandler.java b/source/java/org/alfresco/filesys/smb/server/NetBIOSSessionSocketHandler.java index 9fa5b20a46..977819ad79 100644 --- a/source/java/org/alfresco/filesys/smb/server/NetBIOSSessionSocketHandler.java +++ b/source/java/org/alfresco/filesys/smb/server/NetBIOSSessionSocketHandler.java @@ -20,7 +20,6 @@ import java.net.InetAddress; import java.net.Socket; import java.net.SocketException; -import org.alfresco.filesys.netbios.RFCNetBIOSProtocol; import org.alfresco.filesys.server.config.ServerConfiguration; import org.alfresco.filesys.smb.mailslot.TcpipNetBIOSHostAnnouncer; @@ -154,7 +153,7 @@ public class NetBIOSSessionSocketHandler extends SessionSocketHandler // Create the NetBIOS SMB handler - SessionSocketHandler sessHandler = new NetBIOSSessionSocketHandler(server, RFCNetBIOSProtocol.PORT, config + SessionSocketHandler sessHandler = new NetBIOSSessionSocketHandler(server, config.getNetBIOSSessionPort(), config .getSMBBindAddress(), sockDbg); sessHandler.initialize(); @@ -171,7 +170,7 @@ public class NetBIOSSessionSocketHandler extends SessionSocketHandler // DEBUG if (logger.isDebugEnabled() && sockDbg) - logger.debug("TCP NetBIOS session handler created"); + logger.debug("TCP NetBIOS session handler created on port " + config.getNetBIOSSessionPort()); // Check if a host announcer should be created @@ -188,6 +187,7 @@ public class NetBIOSSessionSocketHandler extends SessionSocketHandler announcer.setDomain(config.getDomainName()); announcer.setComment(config.getComment()); announcer.setBindAddress(config.getSMBBindAddress()); + announcer.setPort(config.getNetBIOSDatagramPort()); // Set the announcement interval @@ -222,7 +222,7 @@ public class NetBIOSSessionSocketHandler extends SessionSocketHandler // DEBUG if (logger.isDebugEnabled() && sockDbg) - logger.debug("TCP NetBIOS host announcer created"); + logger.debug("TCP NetBIOS host announcer created on port " + config.getNetBIOSDatagramPort()); } } } diff --git a/source/java/org/alfresco/filesys/smb/server/SMBServer.java b/source/java/org/alfresco/filesys/smb/server/SMBServer.java index ce08072aec..d95f3df092 100644 --- a/source/java/org/alfresco/filesys/smb/server/SMBServer.java +++ b/source/java/org/alfresco/filesys/smb/server/SMBServer.java @@ -80,15 +80,6 @@ public class SMBServer extends NetworkFileServer implements Runnable private int m_srvType = ServerType.WorkStation + ServerType.Server + ServerType.NTServer; - // Next available session id - - private int m_sessId; - - // Server shutdown flag and server active flag - - private boolean m_shutdown = false; - private boolean m_active = false; - // Server GUID private UUID m_serverGUID; @@ -472,7 +463,7 @@ public class SMBServer extends NetworkFileServer implements Runnable // Clear the server shutdown flag - m_shutdown = false; + setShutdown(false); // Get the list of IP addresses the server is bound to @@ -529,7 +520,7 @@ public class SMBServer extends NetworkFileServer implements Runnable // Wait for incoming connection requests - while (m_shutdown == false) + while (hasShutdown() == false) { // Sleep for a while @@ -569,7 +560,7 @@ public class SMBServer extends NetworkFileServer implements Runnable // Do not report an error if the server has shutdown, closing the server socket // causes an exception to be thrown. - if (m_shutdown == false) + if (hasShutdown() == false) { logger.error("Server error : ", ex); @@ -654,7 +645,7 @@ public class SMBServer extends NetworkFileServer implements Runnable // Indicate that the server is closing - m_shutdown = true; + setShutdown(true); try { diff --git a/source/java/org/alfresco/filesys/smb/server/TcpipSMBSessionSocketHandler.java b/source/java/org/alfresco/filesys/smb/server/TcpipSMBSessionSocketHandler.java index a00f863407..f16db5ff03 100644 --- a/source/java/org/alfresco/filesys/smb/server/TcpipSMBSessionSocketHandler.java +++ b/source/java/org/alfresco/filesys/smb/server/TcpipSMBSessionSocketHandler.java @@ -21,7 +21,6 @@ import java.net.Socket; import java.net.SocketException; import org.alfresco.filesys.server.config.ServerConfiguration; -import org.alfresco.filesys.smb.TcpipSMB; /** * Native SMB Session Socket Handler Class @@ -153,7 +152,7 @@ public class TcpipSMBSessionSocketHandler extends SessionSocketHandler // Create the NetBIOS SMB handler - SessionSocketHandler sessHandler = new TcpipSMBSessionSocketHandler(server, TcpipSMB.PORT, config + SessionSocketHandler sessHandler = new TcpipSMBSessionSocketHandler(server, config.getTcpipSMBPort(), config .getSMBBindAddress(), sockDbg); sessHandler.initialize(); @@ -168,6 +167,6 @@ public class TcpipSMBSessionSocketHandler extends SessionSocketHandler // DEBUG if (logger.isDebugEnabled() && sockDbg) - logger.debug("Native SMB TCP session handler created"); + logger.debug("Native SMB TCP session handler created on port " + config.getTcpipSMBPort()); } } diff --git a/source/java/org/alfresco/filesys/smb/server/repo/CifsHelper.java b/source/java/org/alfresco/filesys/smb/server/repo/CifsHelper.java index 84e0d080db..aa5a889f2d 100644 --- a/source/java/org/alfresco/filesys/smb/server/repo/CifsHelper.java +++ b/source/java/org/alfresco/filesys/smb/server/repo/CifsHelper.java @@ -31,6 +31,7 @@ import org.alfresco.filesys.server.filesys.FileAttribute; import org.alfresco.filesys.server.filesys.FileExistsException; import org.alfresco.filesys.server.filesys.FileInfo; import org.alfresco.filesys.server.filesys.FileName; +import org.alfresco.filesys.util.WildCard; import org.alfresco.model.ContentModel; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.model.FileFolderService; @@ -402,11 +403,7 @@ public class CifsHelper } /** - * Performs an XPath query to get the first-level descendents matching the given path - * - * @param pathRootNodeRef - * @param pathElement - * @return + * Searches for the node or nodes that match the path element for the given parent node */ private List getDirectDescendents(NodeRef pathRootNodeRef, String pathElement) { @@ -416,18 +413,36 @@ public class CifsHelper " Path Root: " + pathRootNodeRef + "\n" + " Path Element: " + pathElement); } - // escape for the Lucene syntax search - String escapedPathElement = SearchLanguageConversion.convertCifsToLucene(pathElement); - // do the lookup - List childInfos = fileFolderService.search( - pathRootNodeRef, - escapedPathElement, - false); - // convert to noderefs - List results = new ArrayList(childInfos.size()); - for (org.alfresco.service.cmr.model.FileInfo info : childInfos) + List results = null; + // if this contains no wildcards, then we can fasttrack it + if (!WildCard.containsWildcards(pathElement)) { - results.add(info.getNodeRef()); + // a specific name is required + NodeRef foundNodeRef = fileFolderService.searchSimple(pathRootNodeRef, pathElement); + if (foundNodeRef == null) + { + results = Collections.emptyList(); + } + else + { + results = Collections.singletonList(foundNodeRef); + } + } + else + { + // escape for the Lucene syntax search + String escapedPathElement = SearchLanguageConversion.convertCifsToLucene(pathElement); + // do the lookup + List childInfos = fileFolderService.search( + pathRootNodeRef, + escapedPathElement, + false); + // convert to noderefs + results = new ArrayList(childInfos.size()); + for (org.alfresco.service.cmr.model.FileInfo info : childInfos) + { + results.add(info.getNodeRef()); + } } // done return results; diff --git a/source/java/org/alfresco/filesys/smb/server/repo/ContentContext.java b/source/java/org/alfresco/filesys/smb/server/repo/ContentContext.java index d774b28437..211bcd684b 100644 --- a/source/java/org/alfresco/filesys/smb/server/repo/ContentContext.java +++ b/source/java/org/alfresco/filesys/smb/server/repo/ContentContext.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005 Alfresco, Inc. + * Copyright (C) 2005-2006 Alfresco, Inc. * * Licensed under the Mozilla Public License version 1.1 * with a permitted attribution clause. You may obtain a @@ -16,8 +16,11 @@ */ package org.alfresco.filesys.smb.server.repo; +import java.util.Enumeration; + import org.alfresco.filesys.server.filesys.*; -import org.alfresco.filesys.smb.server.repo.pseudo.PseudoFile; +import org.alfresco.filesys.smb.server.repo.pseudo.ContentPseudoFileImpl; +import org.alfresco.filesys.smb.server.repo.pseudo.PseudoFileInterface; import org.alfresco.service.cmr.repository.*; /** @@ -42,15 +45,23 @@ public class ContentContext extends DiskDeviceContext private FileStateTable m_stateTable; - // Drag and drop pseudo file - - private PseudoFile m_dragAndDropApp; - // URL pseudo file web path prefix (server/port/webapp) and link file name private String m_urlPathPrefix; private String m_urlFileName; + // Pseudo file interface + + private PseudoFileInterface m_pseudoFileInterface; + + // Desktop actions + + private DesktopActionTable m_desktopActions; + + // I/O control handler + + private IOControlHandler m_ioHandler; + /** * Class constructor * @@ -146,25 +157,108 @@ public class ContentContext extends DiskDeviceContext } /** - * Determine if the drag and drop pseudo file has been configured + * Determine if the pseudo file interface is enabled * * @return boolean */ - public final boolean hasDragAndDropApp() + public final boolean hasPseudoFileInterface() { - return m_dragAndDropApp != null ? true : false; + return m_pseudoFileInterface != null ? true : false; } /** - * Return the drag and drop pseudo file + * Return the pseudo file interface * - * @return PseudoFile + * @return PseudoFileInterface */ - public final PseudoFile getDragAndDropApp() + public final PseudoFileInterface getPseudoFileInterface() { - return m_dragAndDropApp; + return m_pseudoFileInterface; } + /** + * Enable the pseudo file interface for this filesystem + */ + public final void enabledPseudoFileInterface() + { + if ( m_pseudoFileInterface == null) + m_pseudoFileInterface = new ContentPseudoFileImpl(); + } + + /** + * Determine if there are desktop actins configured + * + * @return boolean + */ + public final boolean hasDesktopActions() + { + return m_desktopActions != null ? true : false; + } + + /** + * Return the desktop actions table + * + * @return DesktopActionTable + */ + public final DesktopActionTable getDesktopActions() + { + return m_desktopActions; + } + + /** + * Return the count of desktop actions + * + * @return int + */ + public final int numberOfDesktopActions() + { + return m_desktopActions != null ? m_desktopActions.numberOfActions() : 0; + } + + /** + * Add a desktop action + * + * @param action DesktopAction + * @return boolean + */ + public final boolean addDesktopAction(DesktopAction action) + { + // Check if the desktop actions table has been created + + if ( m_desktopActions == null) + { + m_desktopActions = new DesktopActionTable(); + + // Enable pseudo files + + enabledPseudoFileInterface(); + } + + // Add the action + + return m_desktopActions.addAction(action); + } + + /** + * Determine if custom I/O control handling is enabled for this filesystem + * + * @return boolean + */ + public final boolean hasIOHandler() + { + return m_ioHandler != null ? true : false; + } + + /** + * Return the custom I/O control handler + * + * @return IOControlHandler + */ + public final IOControlHandler getIOHandler() + { + return m_ioHandler; + } + /** * Determine if the URL pseudo file is enabled * @@ -197,16 +291,6 @@ public class ContentContext extends DiskDeviceContext return m_urlFileName; } - /** - * Set the drag and drop application details - * - * @param dragDropApp PseudoFile - */ - public final void setDragAndDropApp(PseudoFile dragDropApp) - { - m_dragAndDropApp = dragDropApp; - } - /** * Set the URL path prefix * @@ -215,6 +299,9 @@ public class ContentContext extends DiskDeviceContext public final void setURLPrefix(String urlPrefix) { m_urlPathPrefix = urlPrefix; + + if ( urlPrefix != null) + enabledPseudoFileInterface(); } /** @@ -225,8 +312,43 @@ public class ContentContext extends DiskDeviceContext public final void setURLFileName(String urlFileName) { m_urlFileName = urlFileName; + + if ( urlFileName != null) + enabledPseudoFileInterface(); } + /** + * Set the desktop actions + * + * @param desktopActions DesktopActionTable + * @param filesysDriver DiskInterface + */ + public final void setDesktopActions(DesktopActionTable desktopActions, DiskInterface filesysDriver) + { + // Enumerate the desktop actions and add to this filesystem + + Enumeration names = desktopActions.enumerateActionNames(); + + while ( names.hasMoreElements()) + { + addDesktopAction( desktopActions.getAction(names.nextElement())); + } + + // If there are desktop actions then create the custom I/O control handler + + if ( numberOfDesktopActions() > 0) + { + // Access the filesystem driver + + ContentDiskDriver contentDriver = (ContentDiskDriver) filesysDriver; + + // Create the custom I/O control handler + + m_ioHandler = new ContentIOControlHandler(); + m_ioHandler.initialize(contentDriver, this); + } + } + /** * Close the filesystem context */ diff --git a/source/java/org/alfresco/filesys/smb/server/repo/ContentDiskDriver.java b/source/java/org/alfresco/filesys/smb/server/repo/ContentDiskDriver.java index 0d57ee05c3..d01bee8699 100644 --- a/source/java/org/alfresco/filesys/smb/server/repo/ContentDiskDriver.java +++ b/source/java/org/alfresco/filesys/smb/server/repo/ContentDiskDriver.java @@ -16,10 +16,8 @@ */ package org.alfresco.filesys.smb.server.repo; -import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; -import java.net.URL; import java.util.List; import javax.transaction.UserTransaction; @@ -50,8 +48,6 @@ import org.alfresco.filesys.smb.SMBStatus; import org.alfresco.filesys.smb.SharingMode; import org.alfresco.filesys.smb.server.SMBSrvSession; import org.alfresco.filesys.smb.server.repo.FileState.FileStateStatus; -import org.alfresco.filesys.smb.server.repo.pseudo.ContentPseudoFileImpl; -import org.alfresco.filesys.smb.server.repo.pseudo.LocalPseudoFile; import org.alfresco.filesys.smb.server.repo.pseudo.MemoryNetworkFile; import org.alfresco.filesys.smb.server.repo.pseudo.PseudoFile; import org.alfresco.filesys.smb.server.repo.pseudo.PseudoFileInterface; @@ -103,18 +99,10 @@ public class ContentDiskDriver implements DiskInterface, IOCtlInterface private SearchService searchService; private ContentService contentService; private PermissionService permissionService; - private CheckOutCheckInService checkInOutService; + private CheckOutCheckInService checkOutInService; private AuthenticationComponent authComponent; - // I/O control handler - - private IOControlHandler m_ioHandler; - - // Pseudo files interface - - private PseudoFileInterface m_pseudoFiles; - /** * Class constructor * @@ -125,6 +113,75 @@ public class ContentDiskDriver implements DiskInterface, IOCtlInterface this.cifsHelper = cifsHelper; } + /** + * Return the CIFS helper + * + * @return CifsHelper + */ + public final CifsHelper getCifsHelper() + { + return this.cifsHelper; + } + + /** + * Return the transaction service + * + * @return TransactionService + */ + public final TransactionService getTransactionService() + { + return this.transactionService; + } + + /** + * Return the node service + * + * @return NodeService + */ + public final NodeService getNodeService() + { + return this.nodeService; + } + + /** + * Return the content service + * + * @return ContentService + */ + public final ContentService getContentService() + { + return this.contentService; + } + + /** + * Return the namespace service + * + * @return NamespaceService + */ + public final NamespaceService getNamespaceService() + { + return this.namespaceService; + } + + /** + * Return the search service + * + * @return SearchService + */ + public final SearchService getSearchService(){ + return this.searchService; + } + + /** + * Return the check in/out service + * + * @return CheckOutInService + */ + public final CheckOutCheckInService getCheckInOutService() + { + return this.checkOutInService; + } + /** * @param contentService the content service */ @@ -156,7 +213,6 @@ public class ContentDiskDriver implements DiskInterface, IOCtlInterface { this.searchService = searchService; } - /** * @param transactionService the transaction service @@ -179,11 +235,11 @@ public class ContentDiskDriver implements DiskInterface, IOCtlInterface /** * Set the check in/out service * - * @param checkInOutService CheckOutCheckInService + * @param checkInService CheckOutInService */ - public void setCheckInOutService(CheckOutCheckInService checkInOutService) + public void setCheckInOutService(CheckOutCheckInService checkInService) { - this.checkInOutService = checkInOutService; + this.checkOutInService = checkInService; } /** @@ -335,52 +391,6 @@ public class ContentDiskDriver implements DiskInterface, IOCtlInterface } } - // Check if the client side drag and drop appliction has been enabled - - ConfigElement dragDropElem = cfg.getChild( "dragAndDrop"); - if ( dragDropElem != null) - { - // Get the pseudo file name and path to the actual file on the local filesystem - - ConfigElement pseudoName = dragDropElem.getChild( "filename"); - ConfigElement appPath = dragDropElem.getChild( "path"); - - if ( pseudoName != null && appPath != null) - { - // Check that the application exists on the local filesystem - - URL appURL = this.getClass().getClassLoader().getResource(appPath.getValue()); - if ( appURL == null) - throw new DeviceContextException("Failed to find drag and drop application, " + appPath.getValue()); - File appFile = new File(appURL.getFile()); - if ( appFile.exists() == false) - throw new DeviceContextException("Drag and drop application not found, " + appPath.getValue()); - - // Create the pseudo file for the drag and drop application - - PseudoFile dragDropPseudo = new LocalPseudoFile( pseudoName.getValue(), appFile.getAbsolutePath()); - context.setDragAndDropApp( dragDropPseudo); - } - - // Initialize the custom I/O handler - - try - { - m_ioHandler = new ContentIOControlHandler(); - m_ioHandler.initialize( this, cifsHelper, transactionService, nodeService, checkInOutService); - } - catch (Exception ex) - { - // Log the error - - logger.error("Failed to initialize I/O control handler", ex); - - // Rethrow the exception - - throw new DeviceContextException("Failed to initialize I/O control handler"); - } - } - // Check if URL link files are enabled ConfigElement urlFileElem = cfg.getChild( "urlFile"); @@ -411,15 +421,6 @@ public class ContentDiskDriver implements DiskInterface, IOCtlInterface } } - // Enable pseudo file support if the drag and drop app and/or URL link files are enabled - - if ( context.hasDragAndDropApp() || context.hasURLFile()) - { - // Create the pseudo file handler - - m_pseudoFiles = new ContentPseudoFileImpl(); - } - // Check if locked files should be marked as offline ConfigElement offlineFiles = cfg.getChild( "offlineFiles"); @@ -442,21 +443,23 @@ public class ContentDiskDriver implements DiskInterface, IOCtlInterface /** * Check if pseudo file support is enabled * + * @param context ContentContext * @return boolean */ - public final boolean hasPseudoFileInterface() + public final boolean hasPseudoFileInterface(ContentContext context) { - return m_pseudoFiles != null ? true : false; + return context.hasPseudoFileInterface(); } /** * Return the pseudo file support implementation - * + * + * @param context ContentContext * @return PseudoFileInterface */ - public final PseudoFileInterface getPseudoFileInterface() + public final PseudoFileInterface getPseudoFileInterface(ContentContext context) { - return m_pseudoFiles; + return context.getPseudoFileInterface(); } /** @@ -506,11 +509,11 @@ public class ContentDiskDriver implements DiskInterface, IOCtlInterface FileInfo finfo = null; - if ( hasPseudoFileInterface()) + if ( hasPseudoFileInterface(ctx)) { // Get the pseudo file - PseudoFile pfile = getPseudoFileInterface().getPseudoFile( session, tree, path); + PseudoFile pfile = getPseudoFileInterface(ctx).getPseudoFile( session, tree, path); if ( pfile != null) { // DEBUG @@ -690,8 +693,8 @@ public class ContentDiskDriver implements DiskInterface, IOCtlInterface // Add pseudo files to the folder being searched - if ( hasPseudoFileInterface()) - getPseudoFileInterface().addPseudoFilesToFolder( sess, tree, paths[0]); + if ( hasPseudoFileInterface(ctx)) + getPseudoFileInterface(ctx).addPseudoFilesToFolder( sess, tree, paths[0]); // Set the search node and file spec @@ -891,11 +894,11 @@ public class ContentDiskDriver implements DiskInterface, IOCtlInterface // Check if pseudo files are enabled - if ( hasPseudoFileInterface()) + if ( hasPseudoFileInterface(ctx)) { // Check if the path is to a pseudo file - PseudoFile pfile = getPseudoFileInterface().getPseudoFile( sess, tree, params.getPath()); + PseudoFile pfile = getPseudoFileInterface(ctx).getPseudoFile( sess, tree, params.getPath()); if ( pfile != null) { // Create a network file to access the pseudo file data @@ -1408,11 +1411,11 @@ public class ContentDiskDriver implements DiskInterface, IOCtlInterface { // Delete the pseudo file - if ( hasPseudoFileInterface()) + if ( hasPseudoFileInterface(ctx)) { // Delete the pseudo file - getPseudoFileInterface().deletePseudoFile( sess, tree, file.getFullName()); + getPseudoFileInterface(ctx).deletePseudoFile( sess, tree, file.getFullName()); } } } @@ -1681,10 +1684,14 @@ public class ContentDiskDriver implements DiskInterface, IOCtlInterface { try { + // Get the device context + + ContentContext ctx = (ContentContext) tree.getContext(); + // Check if pseudo files are enabled - if ( hasPseudoFileInterface() && - getPseudoFileInterface().isPseudoFile( sess, tree, name)) + if ( hasPseudoFileInterface(ctx) && + getPseudoFileInterface(ctx).isPseudoFile( sess, tree, name)) { // Allow the file information to be changed @@ -1980,8 +1987,9 @@ public class ContentDiskDriver implements DiskInterface, IOCtlInterface // Check if the I/O control handler is enabled - if ( m_ioHandler != null) - return m_ioHandler.processIOControl( sess, tree, ctrlCode, fid, dataBuf, isFSCtrl, filter); + ContentContext ctx = (ContentContext) tree.getContext(); + if ( ctx.hasIOHandler()) + return ctx.getIOHandler().processIOControl( sess, tree, ctrlCode, fid, dataBuf, isFSCtrl, filter); else throw new IOControlNotImplementedException(); } diff --git a/source/java/org/alfresco/filesys/smb/server/repo/ContentIOControlHandler.java b/source/java/org/alfresco/filesys/smb/server/repo/ContentIOControlHandler.java index d8f670d132..d9cb60e028 100644 --- a/source/java/org/alfresco/filesys/smb/server/repo/ContentIOControlHandler.java +++ b/source/java/org/alfresco/filesys/smb/server/repo/ContentIOControlHandler.java @@ -19,11 +19,8 @@ package org.alfresco.filesys.smb.server.repo; import java.io.FileNotFoundException; import org.alfresco.filesys.server.SrvSession; -import org.alfresco.filesys.server.filesys.DiskDeviceContext; -import org.alfresco.filesys.server.filesys.FileName; import org.alfresco.filesys.server.filesys.IOControlNotImplementedException; import org.alfresco.filesys.server.filesys.NetworkFile; -import org.alfresco.filesys.server.filesys.NotifyChange; import org.alfresco.filesys.server.filesys.TreeConnection; import org.alfresco.filesys.smb.NTIOCtl; import org.alfresco.filesys.smb.SMBException; @@ -33,7 +30,6 @@ import org.alfresco.filesys.smb.server.repo.ContentDiskDriver; import org.alfresco.filesys.smb.server.repo.IOControlHandler; import org.alfresco.filesys.util.DataBuffer; import org.alfresco.model.ContentModel; -import org.alfresco.service.cmr.coci.CheckOutCheckInService; import org.alfresco.service.cmr.lock.LockType; import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.NodeRef; @@ -55,14 +51,10 @@ public class ContentIOControlHandler implements IOControlHandler private static final Log logger = LogFactory.getLog(ContentIOControlHandler.class); - // Services and helpers - - private CifsHelper cifsHelper; - private TransactionService transactionService; - private NodeService nodeService; - private CheckOutCheckInService checkInOutService; + // Filesystem driver and context private ContentDiskDriver contentDriver; + private ContentContext contentContext; /** * Default constructor @@ -75,21 +67,64 @@ public class ContentIOControlHandler implements IOControlHandler * Initalize the I/O control handler * * @param contentDriver ContentDiskDriver - * @param cifsHelper CifsHelper - * @param transService TransactionService - * @param nodeService NodeService - * @param cociService CheckOutCheckInService + * @param contentContext ContentContext */ - public void initialize( ContentDiskDriver contentDriver, CifsHelper cifsHelper, - TransactionService transService, NodeService nodeService, CheckOutCheckInService cociService) + public void initialize( ContentDiskDriver contentDriver, ContentContext contentContext) { - this.contentDriver = contentDriver; - this.cifsHelper = cifsHelper; - this.transactionService = transService; - this.nodeService = nodeService; - this.checkInOutService = cociService; + this.contentDriver = contentDriver; + this.contentContext = contentContext; } + /** + * Return the CIFS helper + * + * @return CifsHelper + */ + public final CifsHelper getCifsHelper() + { + return contentDriver.getCifsHelper(); + } + + /** + * Return the transaction service + * + * @return TransactionService + */ + public final TransactionService getTransactionService() + { + return contentDriver.getTransactionService(); + } + + /** + * Return the node service + * + * @return NodeService + */ + public final NodeService getNodeService() + { + return contentDriver.getNodeService(); + } + + /** + * Return the filesystem driver + * + * @return ContentDiskDriver + */ + public final ContentDiskDriver getContentDriver() + { + return contentDriver; + } + + /** + * Return the filesystem context + * + * @return ContentContext + */ + public final ContentContext getContentContext() + { + return contentContext; + } + /** * Process a filesystem I/O control request * @@ -140,7 +175,7 @@ public class ContentIOControlHandler implements IOControlHandler { folderNode = contentDriver.getNodeForPath(tree, netFile.getFullName()); - if ( cifsHelper.isDirectory( folderNode) == false) + if ( getCifsHelper().isDirectory( folderNode) == false) folderNode = null; } catch ( FileNotFoundException ex) @@ -155,9 +190,9 @@ public class ContentIOControlHandler implements IOControlHandler // Debug - if ( logger.isInfoEnabled()) { - logger.info("IO control func=0x" + Integer.toHexString(ioFunc) + ", fid=" + fid + ", buffer=" + dataBuf); - logger.info(" Folder nodeRef=" + folderNode); + if ( logger.isDebugEnabled()) { + logger.debug("IO control func=0x" + Integer.toHexString(ioFunc) + ", fid=" + fid + ", buffer=" + dataBuf); + logger.debug(" Folder nodeRef=" + folderNode); } // Check if the I/O control code is one of our custom codes @@ -166,48 +201,49 @@ public class ContentIOControlHandler implements IOControlHandler switch ( ioFunc) { - // Probe to check if this is an Alfresco CIFS server - - case IOControl.CmdProbe: - - // Return a buffer with the signature - - retBuffer = new DataBuffer(IOControl.Signature.length()); - retBuffer.putFixedString(IOControl.Signature, IOControl.Signature.length()); - retBuffer.putInt(IOControl.StsSuccess); - break; - - // Get file information for a file within the current folder - - case IOControl.CmdFileStatus: - - // Process the file status request - - retBuffer = procIOFileStatus( sess, tree, dataBuf, folderNode); - break; - - // Check-in file request - - case IOControl.CmdCheckIn: - - // Process the check-in request - - retBuffer = procIOCheckIn( sess, tree, dataBuf, folderNode, netFile); - break; - - // Check-out file request - - case IOControl.CmdCheckOut: - - // Process the check-out request - - retBuffer = procIOCheckOut( sess, tree, dataBuf, folderNode, netFile); - break; - - // Unknown I/O control code - - default: - throw new IOControlNotImplementedException(); + // Probe to check if this is an Alfresco CIFS server + + case IOControl.CmdProbe: + + // Return a buffer with the signature and protocol version + + retBuffer = new DataBuffer(IOControl.Signature.length()); + retBuffer.putFixedString(IOControl.Signature, IOControl.Signature.length()); + retBuffer.putInt(DesktopAction.StsSuccess); + retBuffer.putInt(IOControl.Version); + break; + + // Get file information for a file within the current folder + + case IOControl.CmdFileStatus: + + // Process the file status request + + retBuffer = procIOFileStatus( sess, tree, dataBuf, folderNode); + break; + + // Get action information for the specified executable path + + case IOControl.CmdGetActionInfo: + + // Process the get action information request + + retBuffer = procGetActionInfo(sess, tree, dataBuf, folderNode, netFile); + break; + + // Run the named action + + case IOControl.CmdRunAction: + + // Process the run action request + + retBuffer = procRunAction(sess, tree, dataBuf, folderNode, netFile); + break; + + // Unknown I/O control code + + default: + throw new IOControlNotImplementedException(); } // Return the reply buffer, may be null @@ -228,12 +264,14 @@ public class ContentIOControlHandler implements IOControlHandler { // Start a transaction - sess.beginTransaction( transactionService, true); + sess.beginTransaction( getTransactionService(), true); // Get the file name from the request String fName = reqBuf.getString( true); - logger.info(" File status, fname=" + fName); + + if ( logger.isDebugEnabled()) + logger.debug(" File status, fname=" + fName); // Create a response buffer @@ -246,7 +284,7 @@ public class ContentIOControlHandler implements IOControlHandler try { - childNode = cifsHelper.getNodeRef( folderNode, fName); + childNode = getCifsHelper().getNodeRef( folderNode, fName); } catch (FileNotFoundException ex) { @@ -258,29 +296,29 @@ public class ContentIOControlHandler implements IOControlHandler { // Return an error response - respBuf.putInt(IOControl.StsFileNotFound); + respBuf.putInt(DesktopAction.StsFileNotFound); return respBuf; } // Check if this is a file or folder node - if ( cifsHelper.isDirectory( childNode)) + if ( getCifsHelper().isDirectory( childNode)) { // Only return the status and node type for folders - respBuf.putInt(IOControl.StsSuccess); + respBuf.putInt(DesktopAction.StsSuccess); respBuf.putInt(IOControl.TypeFolder); } else { // Indicate that this is a file node - respBuf.putInt(IOControl.StsSuccess); + respBuf.putInt(DesktopAction.StsSuccess); respBuf.putInt(IOControl.TypeFile); // Check if this file is a working copy - if ( nodeService.hasAspect( childNode, ContentModel.ASPECT_WORKING_COPY)) + if ( getNodeService().hasAspect( childNode, ContentModel.ASPECT_WORKING_COPY)) { // Indicate that this is a working copy @@ -288,16 +326,16 @@ public class ContentIOControlHandler implements IOControlHandler // Get the owner username and file it was copied from - String owner = (String) nodeService.getProperty( childNode, ContentModel.PROP_WORKING_COPY_OWNER); + String owner = (String) getNodeService().getProperty( childNode, ContentModel.PROP_WORKING_COPY_OWNER); String copiedFrom = null; - if ( nodeService.hasAspect( childNode, ContentModel.ASPECT_COPIEDFROM)) + if ( getNodeService().hasAspect( childNode, ContentModel.ASPECT_COPIEDFROM)) { // Get the path of the file the working copy was generated from - NodeRef fromNode = (NodeRef) nodeService.getProperty( childNode, ContentModel.PROP_COPY_REFERENCE); + NodeRef fromNode = (NodeRef) getNodeService().getProperty( childNode, ContentModel.PROP_COPY_REFERENCE); if ( fromNode != null) - copiedFrom = (String) nodeService.getProperty( fromNode, ContentModel.PROP_NAME); + copiedFrom = (String) getNodeService().getProperty( fromNode, ContentModel.PROP_NAME); } // Pack the owner and copied from values @@ -314,15 +352,15 @@ public class ContentIOControlHandler implements IOControlHandler // Check the lock status of the file - if ( nodeService.hasAspect( childNode, ContentModel.ASPECT_LOCKABLE)) + if ( getNodeService().hasAspect( childNode, ContentModel.ASPECT_LOCKABLE)) { // Get the lock type and owner - String lockTypeStr = (String) nodeService.getProperty( childNode, ContentModel.PROP_LOCK_TYPE); + String lockTypeStr = (String) getNodeService().getProperty( childNode, ContentModel.PROP_LOCK_TYPE); String lockOwner = null; if ( lockTypeStr != null) - lockOwner = (String) nodeService.getProperty( childNode, ContentModel.PROP_LOCK_OWNER); + lockOwner = (String) getNodeService().getProperty( childNode, ContentModel.PROP_LOCK_OWNER); // Pack the lock type, and owner if there is a lock on the file @@ -345,7 +383,7 @@ public class ContentIOControlHandler implements IOControlHandler // Get the content data details for the file - ContentData contentData = (ContentData) nodeService.getProperty( childNode, ContentModel.PROP_CONTENT); + ContentData contentData = (ContentData) getNodeService().getProperty( childNode, ContentModel.PROP_CONTENT); if ( contentData != null) { @@ -371,9 +409,9 @@ public class ContentIOControlHandler implements IOControlHandler return respBuf; } - + /** - * Process the check in I/O request + * Process the get action information request * * @param sess Server session * @param tree Tree connection @@ -382,208 +420,208 @@ public class ContentIOControlHandler implements IOControlHandler * @param netFile NetworkFile for the folder * @return DataBuffer */ - private final DataBuffer procIOCheckIn( SrvSession sess, TreeConnection tree, DataBuffer reqBuf, NodeRef folderNode, + private final DataBuffer procGetActionInfo( SrvSession sess, TreeConnection tree, DataBuffer reqBuf, NodeRef folderNode, NetworkFile netFile) { - // Start a transaction + // Get the executable file name from the request - sess.beginTransaction( transactionService, false); - - // Get the file name from the request - - String fName = reqBuf.getString( true); - boolean keepCheckedOut = reqBuf.getInt() == IOControl.True ? true : false; - - logger.info(" CheckIn, fname=" + fName + ", keepCheckedOut=" + keepCheckedOut); + String exeName = reqBuf.getString( true); + + if ( logger.isDebugEnabled()) + logger.debug(" Get action info, exe=" + exeName); // Create a response buffer DataBuffer respBuf = new DataBuffer(256); respBuf.putFixedString(IOControl.Signature, IOControl.Signature.length()); - // Get the node for the file/folder + // Get the desktop actions list - NodeRef childNode = null; - - try + DesktopActionTable deskActions = contentContext.getDesktopActions(); + if ( deskActions == null) { - childNode = cifsHelper.getNodeRef( folderNode, fName); + respBuf.putInt(DesktopAction.StsNoSuchAction); + return respBuf; } - catch (FileNotFoundException ex) + + // Convert the executable name to an action name + + DesktopAction deskAction = deskActions.getActionViaPseudoName(exeName); + if ( deskAction == null) { + respBuf.putInt(DesktopAction.StsNoSuchAction); + return respBuf; + } + + // Return the desktop action details + + respBuf.putInt(DesktopAction.StsSuccess); + respBuf.putString(deskAction.getName(), true); + respBuf.putInt(deskAction.getAttributes()); + respBuf.putInt(deskAction.getPreProcessActions()); + + String confirmStr = deskAction.getConfirmationString(); + respBuf.putString(confirmStr != null ? confirmStr : "", true); + + // Return the response + + return respBuf; + } + + /** + * Process the run action request + * + * @param sess Server session + * @param tree Tree connection + * @param reqBuf Request buffer + * @param folderNode NodeRef of parent folder + * @param netFile NetworkFile for the folder + * @return DataBuffer + */ + private final DataBuffer procRunAction( SrvSession sess, TreeConnection tree, DataBuffer reqBuf, NodeRef folderNode, + NetworkFile netFile) + { + // Get the name of the action to run + + String actionName = reqBuf.getString(true); + + if ( logger.isDebugEnabled()) + logger.debug(" Run action, name=" + actionName); + + // Create a response buffer + + DataBuffer respBuf = new DataBuffer(256); + respBuf.putFixedString(IOControl.Signature, IOControl.Signature.length()); + + // Find the action handler + + DesktopActionTable deskActions = contentContext.getDesktopActions(); + DesktopAction action = null; + + if ( deskActions != null) + action = deskActions.getAction(actionName); + + if ( action == null) + { + respBuf.putInt(DesktopAction.StsNoSuchAction); + respBuf.putString("", true); + return respBuf; } - // Check if the file/folder was found + // Start a transaction - if ( childNode == null) - { - // Return an error response - - respBuf.putInt(IOControl.StsFileNotFound); - return respBuf; - } - - // Check if this is a file or folder node + sess.beginTransaction( getTransactionService(), true); - if ( cifsHelper.isDirectory( childNode)) + // Get the list of targets for the action + + int targetCnt = reqBuf.getInt(); + DesktopParams deskParams = new DesktopParams(sess, folderNode, netFile); + + while ( reqBuf.getAvailableLength() > 4 && targetCnt > 0) { - // Return an error status, attempt to check in a folder + // Get the desktop target details + + int typ = reqBuf.getInt(); + String path = reqBuf.getString(true); + + DesktopTarget target = new DesktopTarget(typ, path); + deskParams.addTarget(target); + + // Find the node for the target path + + NodeRef childNode = null; - respBuf.putInt(IOControl.StsBadParameter); - } - else - { - // Check if this file is a working copy - - if ( nodeService.hasAspect( childNode, ContentModel.ASPECT_WORKING_COPY)) + try { - try - { - // Check in the file - - checkInOutService.checkin( childNode, null, null, keepCheckedOut); + // Check if the target path is relative to the folder we are working in or the root of the filesystem + + if ( path.startsWith("\\")) + { + // Path is relative to the root of the filesystem + + childNode = getCifsHelper().getNodeRef(contentContext.getRootNode(), path); + } + else + { + // Path is relative to the folder we are working in + + childNode = getCifsHelper().getNodeRef( folderNode, path); + } + } + catch (FileNotFoundException ex) + { + } - // Check in was successful - - respBuf.putInt( IOControl.StsSuccess); - - // Check if there are any file/directory change notify requests active - - DiskDeviceContext diskCtx = (DiskDeviceContext) tree.getContext(); - if (diskCtx.hasChangeHandler()) { - - // Build the relative path to the checked in file - - String fileName = FileName.buildPath( netFile.getFullName(), null, fName, FileName.DOS_SEPERATOR); - - // Queue a file deleted change notification - - diskCtx.getChangeHandler().notifyFileChanged(NotifyChange.ActionRemoved, fileName); - } - } - catch (Exception ex) - { - // Return an error status and message - - respBuf.setPosition( IOControl.Signature.length()); - respBuf.putInt(IOControl.StsError); - respBuf.putString( ex.getMessage(), true, true); - } + // If the node is not valid then return an error status + + if (childNode != null) + { + // Set the node ref for the target + + target.setNode(childNode); } else { - // Not a working copy - - respBuf.putInt(IOControl.StsNotWorkingCopy); + // Build an error response + + respBuf.putInt(DesktopAction.StsFileNotFound); + respBuf.putString("Cannot find noderef for path " + path, true); + + return respBuf; } + + // Update the target count + + targetCnt--; } - // Return the response + // DEBUG - return respBuf; - } - - /** - * Process the check out I/O request - * - * @param sess Server session - * @param tree Tree connection - * @param reqBuf Request buffer - * @param folderNode NodeRef of parent folder - * @param netFile NetworkFile for the folder - * @return DataBuffer - */ - private final DataBuffer procIOCheckOut( SrvSession sess, TreeConnection tree, DataBuffer reqBuf, NodeRef folderNode, - NetworkFile netFile) - { - // Start a transaction + if (logger.isDebugEnabled()) + { + logger.debug(" Desktop params: " + deskParams.numberOfTargetNodes()); + for ( int i = 0; i < deskParams.numberOfTargetNodes(); i++) { + DesktopTarget target = deskParams.getTarget(i); + logger.debug(" " + target); + } + } - sess.beginTransaction( transactionService, false); + // Run the desktop action - // Get the file name from the request - - String fName = reqBuf.getString( true); - - logger.info(" CheckOut, fname=" + fName); - - // Create a response buffer - - DataBuffer respBuf = new DataBuffer(256); - respBuf.putFixedString(IOControl.Signature, IOControl.Signature.length()); - - // Get the node for the file/folder - - NodeRef childNode = null; + DesktopResponse deskResponse = null; try { - childNode = cifsHelper.getNodeRef( folderNode, fName); + // Run the desktop action + + deskResponse = action.runAction(deskParams); } - catch (FileNotFoundException ex) + catch (Exception ex) { + // Create an error response + + deskResponse = new DesktopResponse(DesktopAction.StsError, ex.getMessage()); } - - // Check if the file/folder was found - if ( childNode == null) - { - // Return an error response - - respBuf.putInt(IOControl.StsFileNotFound); - return respBuf; - } - - // Check if this is a file or folder node + // Pack the action response - if ( cifsHelper.isDirectory( childNode)) + if ( deskResponse != null) { - // Return an error status, attempt to check in a folder - - respBuf.putInt(IOControl.StsBadParameter); + // Pack the status + + respBuf.putInt(deskResponse.getStatus()); + respBuf.putString(deskResponse.hasStatusMessage() ? deskResponse.getStatusMessage() : "", true); } else { - try - { - // Check out the file - - NodeRef workingCopyNode = checkInOutService.checkout( childNode); - - // Get the working copy file name - - String workingCopyName = (String) nodeService.getProperty( workingCopyNode, ContentModel.PROP_NAME); - - // Check out was successful, pack the working copy name - - respBuf.putInt( IOControl.StsSuccess); - respBuf.putString( workingCopyName, true, true); - - // Check if there are any file/directory change notify requests active - - DiskDeviceContext diskCtx = (DiskDeviceContext) tree.getContext(); - if (diskCtx.hasChangeHandler()) { - - // Build the relative path to the checked in file - - String fileName = FileName.buildPath( netFile.getFullName(), null, workingCopyName, FileName.DOS_SEPERATOR); - - // Queue a file added change notification - - diskCtx.getChangeHandler().notifyFileChanged(NotifyChange.ActionAdded, fileName); - } - } - catch (Exception ex) - { - // Return an error status and message - - respBuf.setPosition( IOControl.Signature.length()); - respBuf.putInt(IOControl.StsError); - respBuf.putString( ex.getMessage(), true, true); - } + // Pack an error response + + respBuf.putInt(DesktopAction.StsError); + respBuf.putString("Action did not return response", true); } // Return the response - return respBuf; - } + return respBuf; + } } diff --git a/source/java/org/alfresco/filesys/smb/server/repo/DesktopAction.java b/source/java/org/alfresco/filesys/smb/server/repo/DesktopAction.java new file mode 100644 index 0000000000..d17fca8b31 --- /dev/null +++ b/source/java/org/alfresco/filesys/smb/server/repo/DesktopAction.java @@ -0,0 +1,559 @@ +/* + * Copyright (C) 2005-2006 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ + +package org.alfresco.filesys.smb.server.repo; + +import java.io.File; +import java.io.UnsupportedEncodingException; +import java.net.URL; +import java.net.URLDecoder; + +import org.alfresco.config.ConfigElement; +import org.alfresco.filesys.server.filesys.DiskSharedDevice; +import org.alfresco.filesys.smb.server.repo.pseudo.LocalPseudoFile; +import org.alfresco.filesys.smb.server.repo.pseudo.PseudoFile; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.coci.CheckOutCheckInService; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.transaction.TransactionService; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.WebApplicationContextUtils; + +/** + * Desktop Action Class + * + * @author gkspencer + * + */ +public abstract class DesktopAction { + + // Logging + + protected static final Log logger = LogFactory.getLog(DesktopAction.class); + + // Constants + // + // Action attributes + + public static final int AttrTargetFiles = 0x0001; // allow files from the same folder + public static final int AttrTargetFolders = 0x0002; // allow sub-folders from the same folder + public static final int AttrClientFiles = 0x0004; // allow files from a client drive + // File(s) will be copied to the target folder + public static final int AttrClientFolders = 0x0008; // allow folders from a client drive + // Folder(s) will be copied to the target folder + public static final int AttrAlfrescoFiles = 0x0010; // allow files from another path within the Alfresco share + public static final int AttrAlfrescoFolders = 0x0020; // allow folders from another path within the Alfresco share + public static final int AttrMultiplePaths = 0x0040; // run action using multiple paths + // default is to run the action against a single path with the client app calling the action + // multiple times + public static final int AttrAllowNoParams = 0x0080; // allow action to run without parameters + // used when files/folder parameters are optional + + public static final int AttrAnyFiles = AttrTargetFiles + AttrClientFiles + AttrAlfrescoFiles; + public static final int AttrAnyFolders = AttrTargetFolders + AttrClientFolders + AttrAlfrescoFolders; + public static final int AttrAnyFilesFolders = AttrAnyFiles + AttrAnyFolders; + + // Client side pre-processing actions + + public static final int PreCopyToTarget = 0x0001; // copy files/folders from another Alfresco folder to the target folder + public static final int PreConfirmAction = 0x0002; // confirm action, allow user to abort + public static final int PreLocalToWorkingCopy = 0x0004; // local files must match a working copy in the target folder + + // Desktop action status codes + + public static final int StsSuccess = 0; + + public static final int StsError = 1; + public static final int StsFileNotFound = 2; + public static final int StsAccessDenied = 3; + public static final int StsBadParameter = 4; + public static final int StsNotWorkingCopy = 5; + public static final int StsNoSuchAction = 6; + public static final int StsLaunchURL = 7; + public static final int StsCommandLine = 8; + + // Action name + + private String m_name; + + // Pseudo file details + + private PseudoFile m_pseudoFile; + + // Desktop action attributes + + private int m_attributes; + + // Desktop action client side pre-processing control + + private int m_clientPreActions; + + // Filesystem driver and context + + private ContentDiskDriver m_contentDriver; + private ContentContext m_contentContext; + + // Debug enable flag + + private boolean m_debug; + + /** + * Default constructor + */ + protected DesktopAction() + { + } + + /** + * Class constructor + * + * @param attr int + * @param preActions int + */ + protected DesktopAction(int attr, int preActions) + { + setAttributes(attr); + setPreProcessActions(preActions); + } + + /** + * Class constructor + * + * @param name String + */ + protected DesktopAction(String name) + { + m_name = name; + } + + /** + * Return the desktop action attributes + * + * @return int + */ + public final int getAttributes() + { + return m_attributes; + } + + /** + * Check for a specified action attribute + * + * @param attr int + * @return boolean + */ + public final boolean hasAttribute(int attr) + { + return ( m_attributes & attr) != 0 ? true : false; + } + + /** + * Return the desktop action pore-processing actions + * + * @return int + */ + public final int getPreProcessActions() + { + return m_clientPreActions; + } + + /** + * Check for the specified pre-process action + * + * @param pre int + * @return boolean + */ + public final boolean hasPreProcessAction(int pre) + { + return (m_clientPreActions & pre) != 0 ? true : false; + } + + /** + * Return the action name + * + * @return String + */ + public final String getName() + { + return m_name; + } + + /** + * Check if the action has an associated pseudo file + * + * @return boolean + */ + public final boolean hasPseudoFile() + { + return m_pseudoFile != null ? true : false; + } + + /** + * Return the associated pseudo file + * + * @return PseudoFile + */ + public final PseudoFile getPseudoFile() + { + return m_pseudoFile; + } + + /** + * Return the content filesystem driver + * + * @return ContentDiskDriver + */ + public final ContentDiskDriver getDriver() + { + return m_contentDriver; + } + + /** + * Return the filesystem context + * + * @return ContentContext + */ + public final ContentContext getContext() + { + return m_contentContext; + } + + /** + * Return the action confirmation string to be displayed by the client application + * + * @return String + */ + public String getConfirmationString() + { + return null; + } + + /** + * Check if debug output is enabled + * + * @return boolean + */ + public final boolean hasDebug() + { + return m_debug; + } + + /** + * Initialize the desktop action + * + * @param global ConfigElement + * @param config ConfigElement + * @param fileSys DiskSharedDevice + * @exception DesktopActionException + */ + public void initializeAction(ConfigElement global, ConfigElement config, DiskSharedDevice fileSys) + throws DesktopActionException + { + // Perform standard initialization + + standardInitialize(global, config, fileSys); + } + + /** + * Perform standard desktop action initialization + * + * @param global ConfigElement + * @param config ConfigElement + * @param fileSys DiskSharedDevice + * @exception DesktopActionException + */ + public void standardInitialize(ConfigElement global, ConfigElement config, DiskSharedDevice fileSys) + throws DesktopActionException + { + // Save the filesystem device and I/O handler + + if ( fileSys.getDiskInterface() instanceof ContentDiskDriver) + { + m_contentDriver = (ContentDiskDriver) fileSys.getDiskInterface(); + m_contentContext = (ContentContext) fileSys.getDiskContext(); + } + else + throw new DesktopActionException("Desktop action requires content filesystem driver"); + + // Check for standard config values + + ConfigElement elem = config.getChild("name"); + if ( elem != null && elem.getValue().length() > 0) + { + // Set the action name + + setName(elem.getValue()); + } + else + throw new DesktopActionException("Desktop action name not specified"); + + // Get the pseudo file name + + ConfigElement name = config.getChild("filename"); + if ( name == null || name.getValue() == null || name.getValue().length() == 0) + throw new DesktopActionException("Desktop action pseudo name not specified"); + + // Get the local path to the executable + + ConfigElement path = findConfigElement("path", global, config); + if ( path == null || path.getValue() == null || path.getValue().length() == 0) + throw new DesktopActionException("Desktop action executable path not specified"); + + // Check that the application exists on the local filesystem + + URL appURL = this.getClass().getClassLoader().getResource(path.getValue()); + if ( appURL == null) + throw new DesktopActionException("Failed to find drag and drop application, " + path.getValue()); + + // Decode the URL path, it might contain escaped characters + + String appURLPath = null; + try + { + appURLPath = URLDecoder.decode( appURL.getFile(), "UTF-8"); + } + catch ( UnsupportedEncodingException ex) + { + throw new DesktopActionException("Failed to decode drag/drop path, " + ex.getMessage()); + } + + // Check that the drag/drop file exists + + File appFile = new File(appURLPath); + if ( appFile.exists() == false) + throw new DesktopActionException("Drag and drop application not found, " + path.getValue()); + + // Create the pseudo file for the action + + PseudoFile pseudoFile = new LocalPseudoFile(name.getValue(), appFile.getAbsolutePath()); + setPseudoFile(pseudoFile); + + // Check if confirmations should be switched off for the action + + if ( findConfigElement("noConfirm", global, config) != null && hasPreProcessAction(PreConfirmAction)) + setPreProcessActions(getPreProcessActions() - PreConfirmAction); + + // Check if debug output is enabled for the action + + ConfigElement debug = findConfigElement("debug", global, config); + if ( debug != null) + setDebug(true); + + // DEBUG + + if ( logger.isDebugEnabled() && hasDebug()) + logger.debug("Initialized desktop action " + getName() + ", pseudo name " + name.getValue()); + } + + /** + * Find the required configuration element in the local or global config + * + * @param name String + * @param global ConfigElement + * @param local configElement + * @return ConfigElement + */ + private final ConfigElement findConfigElement(String name, ConfigElement global, ConfigElement local) + { + // Check if the required setting is in the local config + + ConfigElement elem = local.getChild(name); + if ( elem == null && global != null) + elem = global.getChild(name); + + return elem; + } + + /** + * Run the desktop action + * + * @param params DesktopParams + * @return DesktopResponse + * @exception + */ + public abstract DesktopResponse runAction(DesktopParams params) + throws DesktopActionException; + + /** + * Return the CIFS helper + * + * @return CifsHelper + */ + protected final CifsHelper getCifsHelper() + { + return m_contentDriver.getCifsHelper(); + } + + /** + * Return the transaction service + * + * @return TransactionService + */ + protected final TransactionService getTransactionService() + { + return m_contentDriver.getTransactionService(); + } + + /** + * Return the node service + * + * @return NodeService + */ + protected final NodeService getNodeService() + { + return m_contentDriver.getNodeService(); + } + + + /** + * Return the content service + * + * @return ContentService + */ + public final ContentService getContentService() + { + return m_contentDriver.getContentService(); + } + + /** + * Return the namespace service + * + * @return NamespaceService + */ + public final NamespaceService getNamespaceService() + { + return m_contentDriver.getNamespaceService(); + } + + /** + * Return the search service + * + * @return SearchService + */ + public final SearchService getSearchService() + { + return m_contentDriver.getSearchService(); + } + + /** + * Return the check in/out service + * + * @return CheckOutInService + */ + public final CheckOutCheckInService getCheckInOutService() + { + return m_contentDriver.getCheckInOutService(); + } + + /** + * Set the action attributes + * + * @param attr int + */ + protected final void setAttributes(int attr) + { + m_attributes = attr; + } + + /** + * Set the client side pre-processing actions + * + * @param pre int + */ + protected final void setPreProcessActions(int pre) + { + m_clientPreActions = pre; + } + + /** + * Set the action name + * + * @param name String + */ + protected final void setName(String name) + { + m_name = name; + } + + /** + * Set the associated pseudo file + * + * @param pseudoFile PseudoFile + */ + protected final void setPseudoFile(PseudoFile pseudoFile) + { + m_pseudoFile = pseudoFile; + } + + /** + * Enable debug output + * + * @param ena boolean + */ + protected final void setDebug(boolean ena) + { + m_debug = ena; + } + + /** + * Equality check + * + * @param obj Object + * @return boolean + */ + @Override + public boolean equals(Object obj) + { + if ( obj instanceof DesktopAction) + { + DesktopAction action = (DesktopAction) obj; + return action.getName().equals(getName()); + } + return false; + } + + /** + * Return the desktop action details as a string + * + * @return String + */ + public String toString() + { + StringBuilder str = new StringBuilder(); + + str.append("["); + str.append(getName()); + str.append(":Attr=0x"); + str.append(Integer.toHexString(getAttributes())); + str.append(":Pre=0x"); + str.append(Integer.toHexString(getPreProcessActions())); + + if ( hasPseudoFile()) + { + str.append(":Pseudo="); + str.append(getPseudoFile().getFileName()); + } + str.append("]"); + + return str.toString(); + } +} diff --git a/source/java/org/alfresco/filesys/smb/server/repo/DesktopActionException.java b/source/java/org/alfresco/filesys/smb/server/repo/DesktopActionException.java new file mode 100644 index 0000000000..1bcda21484 --- /dev/null +++ b/source/java/org/alfresco/filesys/smb/server/repo/DesktopActionException.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ + +package org.alfresco.filesys.smb.server.repo; + +/** + * Desktop Action Exception Class + * + * @author gkspencer + */ +public class DesktopActionException extends Exception { + + private static final long serialVersionUID = 1006648817889605047L; + + // Status code + + private int m_stsCode; + + /** + * Class constructor + * + * @param sts int + * @param msg String + */ + public DesktopActionException(int sts, String msg) + { + super(msg); + m_stsCode = sts; + } + + /** + * Class constructor + * + * @param s String + */ + public DesktopActionException(String s) + { + super(s); + } + + /** + * Class constructor + * + * @param s String + * @param ex Exception + */ + public DesktopActionException(String s, Throwable ex) + { + super(s, ex); + } + + /** + * Return the status code + * + * @return int + */ + public final int getStatusCode() + { + return m_stsCode; + } +} diff --git a/source/java/org/alfresco/filesys/smb/server/repo/DesktopActionTable.java b/source/java/org/alfresco/filesys/smb/server/repo/DesktopActionTable.java new file mode 100644 index 0000000000..2bbf452fc4 --- /dev/null +++ b/source/java/org/alfresco/filesys/smb/server/repo/DesktopActionTable.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ + +package org.alfresco.filesys.smb.server.repo; + +import java.util.Enumeration; +import java.util.Hashtable; + +/** + * Desktop Action Table Class + * + *

Contains a list of desktop actions indexed by action name. + * + * @author gkspencer +*/ +public class DesktopActionTable { + + // Table of actions, indexed by action name and pseudo file name + + private Hashtable m_actions; + private Hashtable m_actionsPseudo; + + /** + * Default constructor + */ + public DesktopActionTable() + { + m_actions = new Hashtable(); + m_actionsPseudo = new Hashtable(); + } + + /** + * Find a named action + * + * @param name String + * @return DesktopAction + */ + public final DesktopAction getAction(String name) + { + return m_actions.get(name); + } + + /** + * Find an action via the pseudo file name + * + * @param pseudoName String + * @return DesktopAction + */ + public final DesktopAction getActionViaPseudoName(String pseudoName) + { + return m_actionsPseudo.get(pseudoName.toUpperCase()); + } + + /** + * Return the count of actions + * + * @return int + */ + public final int numberOfActions() + { + return m_actions.size(); + } + + /** + * Add an action + * + * @param action DesktopAction + * @return boolean + */ + public final boolean addAction(DesktopAction action) + { + if ( m_actions.get( action.getName()) == null) + { + m_actions.put(action.getName(), action); + m_actionsPseudo.put(action.getPseudoFile().getFileName().toUpperCase(), action); + return true; + } + return false; + } + + /** + * Enumerate the action names + * + * @return Enumeration + */ + public final Enumeration enumerateActionNames() + { + return m_actions.keys(); + } + + /** + * Remove an action + * + * @param name String + * @return DesktopAction + */ + public final DesktopAction removeAction(String name) + { + DesktopAction action = m_actions.remove(name); + if ( action != null) + m_actionsPseudo.remove(action.getPseudoFile().getFileName().toUpperCase()); + return action; + } +} diff --git a/source/java/org/alfresco/filesys/smb/server/repo/DesktopParams.java b/source/java/org/alfresco/filesys/smb/server/repo/DesktopParams.java new file mode 100644 index 0000000000..1a2e294135 --- /dev/null +++ b/source/java/org/alfresco/filesys/smb/server/repo/DesktopParams.java @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2005-2006 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.smb.server.repo; + +import java.util.ArrayList; +import java.util.List; + +import org.alfresco.filesys.server.SrvSession; +import org.alfresco.filesys.server.filesys.NetworkFile; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Desktop Parameters Class + * + *

Contains the parameters for a desktop action request from the client side application. + * + * @author gkspencer + */ +public class DesktopParams { + + // File server session + + private SrvSession m_session; + + // Folder node that the actions are working in + + private NodeRef m_folderNode; + + // Network file for the folder node + + private NetworkFile m_folderFile; + + // List of file/folder/node targets for the action + + private List m_targets; + + /** + * Default constructor + */ + public DesktopParams() + { + } + + /** + * Class constructor + * + * @param sess SrvSession + * @param folderNode NodeRef + * @param folderFile NetworkFile + */ + public DesktopParams(SrvSession sess, NodeRef folderNode, NetworkFile folderFile) + { + m_session = sess; + m_folderNode = folderNode; + m_folderFile = folderFile; + } + + /** + * Return the count of target nodes for the action + * + * @return int + */ + public final int numberOfTargetNodes() + { + return m_targets != null ? m_targets.size() : 0; + } + + /** + * Return the file server session + * + * @return SrvSession + */ + public final SrvSession getSession() + { + return m_session; + } + + /** + * Return the working directory node + * + * @return NodeRef + */ + public final NodeRef getFolderNode() + { + return m_folderNode; + } + + /** + * Return the folder network file + * + * @return NetworkFile + */ + public final NetworkFile getFolder() + { + return m_folderFile; + } + + /** + * Set the folder network file + * + * @param netFile NetworkFile + */ + public final void setFolder(NetworkFile netFile) + { + m_folderFile = netFile; + } + + /** + * Return the required target + * + * @param idx int + * @return DesktopTarget + */ + public final DesktopTarget getTarget(int idx) + { + DesktopTarget deskTarget = null; + + if ( m_targets != null && idx >= 0 && idx < m_targets.size()) + deskTarget = m_targets.get(idx); + + return deskTarget; + } + + /** + * Add a target node for the action + * + * @param target DesktopTarget + */ + public final void addTarget(DesktopTarget target) + { + if ( m_targets == null) + m_targets = new ArrayList(); + m_targets.add(target); + } + + /** + * Return the desktop parameters as a string + * + * @return String + */ + public String toString() + { + StringBuilder str = new StringBuilder(); + + str.append("["); + str.append("Targets="); + str.append(numberOfTargetNodes()); + str.append("]"); + + return str.toString(); + } +} diff --git a/source/java/org/alfresco/filesys/smb/server/repo/DesktopResponse.java b/source/java/org/alfresco/filesys/smb/server/repo/DesktopResponse.java new file mode 100644 index 0000000000..cf69283a7c --- /dev/null +++ b/source/java/org/alfresco/filesys/smb/server/repo/DesktopResponse.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2005-2006 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.smb.server.repo; + +import java.util.ArrayList; +import java.util.List; + +/** + * Desktop Response Class + * + *

Holds the status code, optional status message and optional values returned by a desktop action. + * + * @author gkspencer + */ +public class DesktopResponse { + + // Desktop action status and optional status message + + private int m_status; + private String m_statusMsg; + + // Optional return values + + private List m_responseValues; + + /** + * Class constructor + * + * @param sts int + */ + public DesktopResponse(int sts) + { + m_status = sts; + } + + /** + * Class constructor + * + * @param sts int + * @param msg String + */ + public DesktopResponse(int sts, String msg) + { + m_status = sts; + m_statusMsg = msg; + } + + /** + * Return the status code + * + * @return int + */ + public final int getStatus() + { + return m_status; + } + + /** + * Determine if there is an optional status message + * + * @return boolean + */ + public final boolean hasStatusMessage() + { + return m_statusMsg != null ? true : false; + } + + /** + * Return the status message + * + * @return String + */ + public final String getStatusMessage() + { + return m_statusMsg; + } + + /** + * Determine if there are optional response values + * + * @return boolean + */ + public final boolean hasResponseValues() + { + return m_responseValues != null ? true : false; + } + + /** + * Return the count of response values + * + * @return int + */ + public final int numberOfResponseValues() + { + return m_responseValues != null ? m_responseValues.size() : 0; + } + + /** + * Get the response value list + * + * @return List + */ + public final List getResponseValues() + { + return m_responseValues; + } + + /** + * Add a response value + * + * @param respObj Object + */ + public final void addResponseValue(Object respObj) + { + if ( m_responseValues == null) + m_responseValues = new ArrayList(); + + m_responseValues.add(respObj); + } + + /** + * Set the status code and message + * + * @param sts int + * @param msg String + */ + public final void setStatus(int sts, String msg) + { + m_status = sts; + m_statusMsg = msg; + } + + /** + * Return the desktop response as a string + * + * @return String + */ + public String toString() + { + StringBuilder str = new StringBuilder(); + + str.append("["); + str.append(getStatus()); + str.append(":"); + if ( hasStatusMessage()) + str.append(getStatusMessage()); + else + str.append(""); + str.append(":Values="); + str.append(numberOfResponseValues()); + str.append("]"); + + return str.toString(); + } +} diff --git a/source/java/org/alfresco/filesys/smb/server/repo/DesktopTarget.java b/source/java/org/alfresco/filesys/smb/server/repo/DesktopTarget.java new file mode 100644 index 0000000000..877999c556 --- /dev/null +++ b/source/java/org/alfresco/filesys/smb/server/repo/DesktopTarget.java @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2005-2006 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.smb.server.repo; + +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Desktop Target Class + * + *

Contains the details of a target file/folder/node for a desktop action. + * + * @author gkspencer + */ +public class DesktopTarget { + + // Desktop target types + + public static final int TargetFile = 0; + public static final int TargetFolder = 1; + public static final int TargetCopiedFile = 2; + public static final int TargetCopiedFolder = 3; + public static final int TargetNodeRef = 4; + + // Target type + + private int m_type; + + // Target path/id + + private String m_target; + + // Associated noderef + + private NodeRef m_noderef; + + /** + * class constructor + * + * @param typ int + * @param path String + */ + public DesktopTarget(int typ, String path) + { + m_type = typ; + m_target = path; + } + + /** + * Return the target type + * + * @return int + */ + public final int isType() + { + return m_type; + } + + /** + * Return the target path/id + * + * @return String + */ + public final String getTarget() + { + return m_target; + } + + /** + * Check if the associated node is valid + * + * @return boolean + */ + public final boolean hasNodeRef() + { + return m_noderef != null ? true : false; + } + + /** + * Return the associated node + * + * @return NodeRef + */ + public final NodeRef getNode() + { + return m_noderef; + } + + /** + * Return the target type as a string + * + * @return String + */ + public final String getTypeAsString() + { + String str = null; + + switch( isType()) + { + case TargetFile: + str = "File"; + break; + case TargetFolder: + str = "Folder"; + break; + case TargetCopiedFile: + str = "File Copy"; + break; + case TargetCopiedFolder: + str = "Folder Copy"; + break; + case TargetNodeRef: + str = "NodeRef"; + break; + } + + return str; + } + + /** + * Set the associated node + * + * @param node NodeRef + */ + public final void setNode(NodeRef node) + { + m_noderef = node; + } + + /** + * Return the desktop target as a string + * + * @return String + */ + public String toString() + { + StringBuilder str = new StringBuilder(); + + str.append("["); + str.append(getTypeAsString()); + str.append(":"); + str.append(getTarget()); + + if ( hasNodeRef()) + { + str.append(":"); + str.append(getNode()); + } + str.append("]"); + + return str.toString(); + } +} diff --git a/source/java/org/alfresco/filesys/smb/server/repo/IOControl.java b/source/java/org/alfresco/filesys/smb/server/repo/IOControl.java index 1eac93cd41..75d7009328 100644 --- a/source/java/org/alfresco/filesys/smb/server/repo/IOControl.java +++ b/source/java/org/alfresco/filesys/smb/server/repo/IOControl.java @@ -21,7 +21,7 @@ import org.alfresco.filesys.smb.NTIOCtl; /** * Content Disk Driver I/O Control Codes Class * - *

contains I/O control codes and status codes used by the content disk driver I/O control + *

Contains I/O control codes and status codes used by the content disk driver I/O control * implementation. * * @author gkspencer @@ -30,24 +30,20 @@ public class IOControl { // Custom I/O control codes - public static final int CmdProbe = NTIOCtl.FsCtlCustom; - public static final int CmdFileStatus = NTIOCtl.FsCtlCustom + 1; - public static final int CmdCheckOut = NTIOCtl.FsCtlCustom + 2; - public static final int CmdCheckIn = NTIOCtl.FsCtlCustom + 3; + public static final int CmdProbe = NTIOCtl.FsCtlCustom; + public static final int CmdFileStatus = NTIOCtl.FsCtlCustom + 1; + // Version 1 CmdCheckOut = NTIOCtl.FsCtlCustom + 2 + // Version 1 CmdCheckIn = NTIOCtl.FsCtlCustom + 3 + public static final int CmdGetActionInfo = NTIOCtl.FsCtlCustom + 4; + public static final int CmdRunAction = NTIOCtl.FsCtlCustom + 5; // I/O control request/response signature public static final String Signature = "ALFRESCO"; - // I/O control status codes + // I/O control interface version id - public static final int StsSuccess = 0; - - public static final int StsError = 1; - public static final int StsFileNotFound = 2; - public static final int StsAccessDenied = 3; - public static final int StsBadParameter = 4; - public static final int StsNotWorkingCopy = 5; + public static final int Version = 2; // Boolean field values diff --git a/source/java/org/alfresco/filesys/smb/server/repo/IOControlHandler.java b/source/java/org/alfresco/filesys/smb/server/repo/IOControlHandler.java index 39eafacc9f..255f0c5909 100644 --- a/source/java/org/alfresco/filesys/smb/server/repo/IOControlHandler.java +++ b/source/java/org/alfresco/filesys/smb/server/repo/IOControlHandler.java @@ -18,7 +18,6 @@ package org.alfresco.filesys.smb.server.repo; import org.alfresco.filesys.server.filesys.IOCtlInterface; -import org.alfresco.service.cmr.coci.CheckOutCheckInService; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.transaction.TransactionService; @@ -34,11 +33,7 @@ public interface IOControlHandler extends IOCtlInterface * * * @param contentDriver ContentDiskDriver - * @param cifsHelper CifsHelper - * @param transService TransactionService - * @param nodeService NodeService - * @param cociService CheckOutCheckInService + * @param contentContext ContentContext */ - public void initialize( ContentDiskDriver contentDriver, CifsHelper cifsHelper, - TransactionService transService, NodeService nodeService, CheckOutCheckInService cociService); + public void initialize( ContentDiskDriver contentDriver, ContentContext contentContext); } diff --git a/source/java/org/alfresco/filesys/smb/server/repo/desk/CheckInOutDesktopAction.java b/source/java/org/alfresco/filesys/smb/server/repo/desk/CheckInOutDesktopAction.java new file mode 100644 index 0000000000..0f98e425e3 --- /dev/null +++ b/source/java/org/alfresco/filesys/smb/server/repo/desk/CheckInOutDesktopAction.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2005-2006 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.smb.server.repo.desk; + +import org.alfresco.filesys.server.filesys.FileName; +import org.alfresco.filesys.server.filesys.NotifyChange; +import org.alfresco.filesys.smb.server.repo.DesktopAction; +import org.alfresco.filesys.smb.server.repo.DesktopParams; +import org.alfresco.filesys.smb.server.repo.DesktopResponse; +import org.alfresco.filesys.smb.server.repo.DesktopTarget; +import org.alfresco.model.ContentModel; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Check In/Out Desktop Action Class + * + *

Provides check in/out functionality via CIFS using the desktop action interface. + * + * @author gkspencer + */ +public class CheckInOutDesktopAction extends DesktopAction { + + /** + * Class constructor + */ + public CheckInOutDesktopAction() + { + super( DesktopAction.AttrAnyFiles, DesktopAction.PreConfirmAction + DesktopAction.PreCopyToTarget + DesktopAction.PreLocalToWorkingCopy); + } + + @Override + public String getConfirmationString() { + return "Run check in/out action"; + } + + /** + * Run the desktop action + * + * @param params DesktopParams + * @return DesktopResponse + */ + @Override + public DesktopResponse runAction(DesktopParams params) { + + // check if there are any files/folders to process + + if ( params.numberOfTargetNodes() == 0) + return new DesktopResponse(StsSuccess); + + // Start a transaction + + params.getSession().beginTransaction(getTransactionService(), false); + + // Process the list of target nodes + + DesktopResponse response = new DesktopResponse(StsSuccess); + + for ( int idx = 0; idx < params.numberOfTargetNodes(); idx++) + { + // Get the current target node + + DesktopTarget target = params.getTarget(idx); + + // Check if the node is a working copy + + if ( getNodeService().hasAspect( target.getNode(), ContentModel.ASPECT_WORKING_COPY)) + { + try + { + // Check in the file + + getCheckInOutService().checkin( target.getNode(), null, null, false); + + // Check if there are any file/directory change notify requests active + + if ( getContext().hasChangeHandler()) { + + // Build the relative path to the checked in file + + String fileName = null; + + if ( target.getTarget().startsWith(FileName.DOS_SEPERATOR_STR)) + { + // Path is already relative to filesystem root + + fileName = target.getTarget(); + } + else + { + // Build a root relative path for the file + + fileName = FileName.buildPath( params.getFolder().getFullName(), null, target.getTarget(), FileName.DOS_SEPERATOR); + } + + // Queue a file deleted change notification + + getContext().getChangeHandler().notifyFileChanged(NotifyChange.ActionRemoved, fileName); + } + } + catch (Exception ex) + { + // Return an error status and message + + response.setStatus(StsError, "Checkin failed for " + target.getTarget() + ", " + ex.getMessage()); + } + } + else + { + try + { + // Check out the file + + NodeRef workingCopyNode = getCheckInOutService().checkout( target.getNode()); + + // Get the working copy file name + + String workingCopyName = (String) getNodeService().getProperty( workingCopyNode, ContentModel.PROP_NAME); + + // Check out was successful, pack the working copy name + + response.setStatus(StsSuccess, "Checked out working copy " + workingCopyName); + + // Check if there are any file/directory change notify requests active + + if ( getContext().hasChangeHandler()) { + + // Build the relative path to the checked in file + + String fileName = FileName.buildPath( params.getFolder().getFullName(), null, workingCopyName, FileName.DOS_SEPERATOR); + + // Queue a file added change notification + + getContext().getChangeHandler().notifyFileChanged(NotifyChange.ActionAdded, fileName); + } + } + catch (Exception ex) + { + // Return an error status and message + + response.setStatus(StsError, "Failed to checkout " + target.getTarget() + ", " + ex.getMessage()); + } + } + } + + // Return a success status for now + + return response; + } +} diff --git a/source/java/org/alfresco/filesys/smb/server/repo/desk/CmdLineDesktopAction.java b/source/java/org/alfresco/filesys/smb/server/repo/desk/CmdLineDesktopAction.java new file mode 100644 index 0000000000..6744204fdf --- /dev/null +++ b/source/java/org/alfresco/filesys/smb/server/repo/desk/CmdLineDesktopAction.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2005-2006 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.smb.server.repo.desk; + +import org.alfresco.filesys.smb.server.repo.DesktopAction; +import org.alfresco.filesys.smb.server.repo.DesktopParams; +import org.alfresco.filesys.smb.server.repo.DesktopResponse; + +/** + * Command Line Desktop Action Class + * + *

Simple desktop action that returns a test command line. + * + * @author gkspencer + */ +public class CmdLineDesktopAction extends DesktopAction { + + /** + * Class constructor + */ + public CmdLineDesktopAction() + { + super( 0, PreConfirmAction); + } + + @Override + public String getConfirmationString() { + return "Run commandline action"; + } + + @Override + public DesktopResponse runAction(DesktopParams params) { + + // Return a URL in the status message + + return new DesktopResponse(StsCommandLine, "C:\\Windows\\notepad.exe"); + } +} diff --git a/source/java/org/alfresco/filesys/smb/server/repo/desk/EchoDesktopAction.java b/source/java/org/alfresco/filesys/smb/server/repo/desk/EchoDesktopAction.java new file mode 100644 index 0000000000..2293d3efff --- /dev/null +++ b/source/java/org/alfresco/filesys/smb/server/repo/desk/EchoDesktopAction.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2005-2006 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.smb.server.repo.desk; + +import java.util.Date; + +import org.alfresco.filesys.smb.server.repo.DesktopAction; +import org.alfresco.filesys.smb.server.repo.DesktopParams; +import org.alfresco.filesys.smb.server.repo.DesktopResponse; + +/** + * Echo Desktop Action Class + * + *

Simple desktop action that echoes back the received string. + * + * @author gkspencer + */ +public class EchoDesktopAction extends DesktopAction { + + /** + * Class constructor + */ + public EchoDesktopAction() + { + super( 0, PreConfirmAction); + } + + @Override + public String getConfirmationString() { + return "Run echo action"; + } + + @Override + public DesktopResponse runAction(DesktopParams params) { + + // Return a text message + + return new DesktopResponse(StsSuccess, "Test message from echo action at " + new Date()); + } +} diff --git a/source/java/org/alfresco/filesys/smb/server/repo/desk/URLDesktopAction.java b/source/java/org/alfresco/filesys/smb/server/repo/desk/URLDesktopAction.java new file mode 100644 index 0000000000..ca63252963 --- /dev/null +++ b/source/java/org/alfresco/filesys/smb/server/repo/desk/URLDesktopAction.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2005-2006 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.smb.server.repo.desk; + +import org.alfresco.filesys.smb.server.repo.DesktopAction; +import org.alfresco.filesys.smb.server.repo.DesktopParams; +import org.alfresco.filesys.smb.server.repo.DesktopResponse; + +/** + * URL Desktop Action Class + * + *

Simple desktop action that returns a test URL. + * + * @author gkspencer + */ +public class URLDesktopAction extends DesktopAction { + + /** + * Class constructor + */ + public URLDesktopAction() + { + super( 0, PreConfirmAction); + } + + @Override + public String getConfirmationString() { + return "Run URL action"; + } + + @Override + public DesktopResponse runAction(DesktopParams params) { + + // Return a URL in the status message + + return new DesktopResponse(StsLaunchURL, "http://www.alfresco.com"); + } +} diff --git a/source/java/org/alfresco/filesys/smb/server/repo/pseudo/ContentPseudoFileImpl.java b/source/java/org/alfresco/filesys/smb/server/repo/pseudo/ContentPseudoFileImpl.java index 3d90a835cc..24f8e08896 100644 --- a/source/java/org/alfresco/filesys/smb/server/repo/pseudo/ContentPseudoFileImpl.java +++ b/source/java/org/alfresco/filesys/smb/server/repo/pseudo/ContentPseudoFileImpl.java @@ -16,11 +16,15 @@ */ package org.alfresco.filesys.smb.server.repo.pseudo; +import java.util.Enumeration; + import org.alfresco.filesys.server.SrvSession; import org.alfresco.filesys.server.filesys.FileName; import org.alfresco.filesys.server.filesys.TreeConnection; import org.alfresco.filesys.smb.server.SMBSrvSession; import org.alfresco.filesys.smb.server.repo.ContentContext; +import org.alfresco.filesys.smb.server.repo.DesktopAction; +import org.alfresco.filesys.smb.server.repo.DesktopActionTable; import org.alfresco.filesys.smb.server.repo.FileState; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -126,27 +130,40 @@ public class ContentPseudoFileImpl implements PseudoFileInterface boolean isCIFS = sess instanceof SMBSrvSession; - // Add the drag and drop pseudo file, if enabled + // Add the desktop action pseudo files - if ( isCIFS && ctx.hasDragAndDropApp()) + if ( isCIFS && ctx.numberOfDesktopActions() > 0) { // If the file state is null create a file state for the path if ( fstate == null) ctx.getStateTable().findFileState( path, true, true); - // Enable the drag and drop pseudo file + // Add the desktop action pseudo files - fstate.addPseudoFile( ctx.getDragAndDropApp()); + DesktopActionTable actions = ctx.getDesktopActions(); + Enumeration actionNames = actions.enumerateActionNames(); + + while(actionNames.hasMoreElements()) + { + // Get the current desktop action + + String name = actionNames.nextElement(); + DesktopAction action = actions.getAction(name); + + // Add the pseudo file for the desktop action + + if ( action.hasPseudoFile()) + { + fstate.addPseudoFile( action.getPseudoFile()); + pseudoCnt++; - // Update the count of pseudo files added - - pseudoCnt++; - - // DEBUG - - if ( logger.isInfoEnabled()) - logger.info("Added drag/drop pseudo file for " + path); + // DEBUG + + if ( logger.isInfoEnabled()) + logger.info("Added desktop action " + action.getName() + " for " + path); + } + } } // Add the URL link pseudo file, if enabled diff --git a/source/java/org/alfresco/model/ContentModel.java b/source/java/org/alfresco/model/ContentModel.java index 928898b10d..2048326cbf 100644 --- a/source/java/org/alfresco/model/ContentModel.java +++ b/source/java/org/alfresco/model/ContentModel.java @@ -40,6 +40,9 @@ public interface ContentModel // tag for incomplete nodes static final QName ASPECT_INCOMPLETE = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "incomplete"); + // tag for temporary nodes + static final QName ASPECT_TEMPORARY = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "temporary"); + // archived nodes aspect constants static final QName ASPECT_ARCHIVED = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "archived"); static final QName PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "archivedOriginalParentAssoc"); @@ -70,6 +73,7 @@ public interface ContentModel static final QName PROP_SYS_VERSION_MINOR = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "versionMinor"); static final QName PROP_SYS_VERSION_REVISION = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "versionRevision"); static final QName PROP_SYS_VERSION_LABEL = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "versionLabel"); + static final QName PROP_SYS_VERSION_BUILD = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "versionBuild"); static final QName PROP_SYS_VERSION_SCHEMA = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "versionSchema"); static final QName PROP_SYS_VERSION_EDITION = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "versionEdition"); @@ -173,18 +177,26 @@ public interface ContentModel public static final QName TYPE_LINK = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "link"); public static final QName PROP_LINK_DESTINATION = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "destination"); + // email aspect + public static final QName ASPECT_MAILED = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "emailed"); + public static final QName PROP_SENTDATE = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "sentdate"); + public static final QName PROP_ORIGINATOR = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "originator"); + public static final QName PROP_ADDRESSEE = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "addressee"); + public static final QName PROP_ADDRESSEES = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "addressees"); + public static final QName PROP_SUBJECT = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "subjectline"); + // // Application Model Definitions // - // workflow - static final QName ASPECT_SIMPLE_WORKFLOW = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "simpleworkflow"); - static final QName PROP_APPROVE_STEP = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "approveStep"); - static final QName PROP_APPROVE_FOLDER = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "approveFolder"); - static final QName PROP_APPROVE_MOVE = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "approveMove"); - static final QName PROP_REJECT_STEP = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "rejectStep"); - static final QName PROP_REJECT_FOLDER = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "rejectFolder"); - static final QName PROP_REJECT_MOVE = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "rejectMove"); + // workflow + static final QName ASPECT_SIMPLE_WORKFLOW = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "simpleworkflow"); + static final QName PROP_APPROVE_STEP = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "approveStep"); + static final QName PROP_APPROVE_FOLDER = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "approveFolder"); + static final QName PROP_APPROVE_MOVE = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "approveMove"); + static final QName PROP_REJECT_STEP = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "rejectStep"); + static final QName PROP_REJECT_FOLDER = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "rejectFolder"); + static final QName PROP_REJECT_MOVE = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "rejectMove"); // ui facets aspect static final QName ASPECT_UIFACETS = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "uifacets"); @@ -194,7 +206,7 @@ public interface ContentModel static final QName ASPECT_INLINEEDITABLE = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "inlineeditable"); static final QName PROP_EDITINLINE = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "editInline"); - // configurable + // configurable aspect static final QName ASPECT_CONFIGURABLE = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "configurable"); static final QName TYPE_CONFIGURATIONS = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "configurations"); static final QName ASSOC_CONFIGURATIONS = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "configurations"); @@ -203,6 +215,10 @@ public interface ContentModel static final QName TYPE_FILELINK = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "filelink"); static final QName TYPE_FOLDERLINK = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "folderlink"); + // feed source aspect + static final QName ASPECT_FEEDSOURCE = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "feedsource"); + static final QName PROP_FEEDTEMPLATE = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "template"); + // // User Model Definitions diff --git a/source/java/org/alfresco/repo/action/ActionImpl.java b/source/java/org/alfresco/repo/action/ActionImpl.java index b92579f5ae..c568db742e 100644 --- a/source/java/org/alfresco/repo/action/ActionImpl.java +++ b/source/java/org/alfresco/repo/action/ActionImpl.java @@ -40,6 +40,9 @@ public class ActionImpl extends ParameterizedItemImpl */ private static final long serialVersionUID = 3258135760426186548L; + /** The node reference for the action */ + private NodeRef nodeRef; + /** * The title */ @@ -90,11 +93,6 @@ public class ActionImpl extends ParameterizedItemImpl */ private String runAsUserName; - /** - * The owning node reference - */ - private NodeRef owningNodeRef; - /** * The chain of actions that have lead to this action */ @@ -108,30 +106,32 @@ public class ActionImpl extends ParameterizedItemImpl /** * Constructor * + * @param nodeRef the action node reference (null if not saved) * @param id the action id * @param actionDefinitionName the name of the action definition */ - public ActionImpl(String id, String actionDefinitionName, NodeRef owningNodeRef) + public ActionImpl(NodeRef nodeRef, String id, String actionDefinitionName) { - this(id, actionDefinitionName, owningNodeRef, null); + this(nodeRef, id, actionDefinitionName, null); } /** * Constructor * + * @param nodeRef the action node reference (null if not saved) * @param id the action id * @param actionDefinitionName the action definition name * @param parameterValues the parameter values */ public ActionImpl( + NodeRef nodeRef, String id, String actionDefinitionName, - NodeRef owningNodeRef, Map parameterValues) { super(id, parameterValues); + this.nodeRef = nodeRef; this.actionDefinitionName = actionDefinitionName; - this.owningNodeRef = owningNodeRef; } /** @@ -165,19 +165,6 @@ public class ActionImpl extends ParameterizedItemImpl { this.description = description; } - - /** - * @see org.alfresco.service.cmr.action.Action#getOwningNodeRef() - */ - public NodeRef getOwningNodeRef() - { - return this.owningNodeRef; - } - - public void setOwningNodeRef(NodeRef owningNodeRef) - { - this.owningNodeRef = owningNodeRef; - } /** * @see org.alfresco.service.cmr.action.Action#getExecuteAsychronously() @@ -392,4 +379,22 @@ public class ActionImpl extends ParameterizedItemImpl { this.runAsUserName = runAsUserName; } + + /** + * @see org.alfresco.service.cmr.action.Action#getNodeRef() + */ + public NodeRef getNodeRef() + { + return this.nodeRef; + } + + /** + * Set the node reference + * + * @param nodeRef the node reference + */ + public void setNodeRef(NodeRef nodeRef) + { + this.nodeRef = nodeRef; + } } diff --git a/source/java/org/alfresco/repo/action/ActionImplTest.java b/source/java/org/alfresco/repo/action/ActionImplTest.java index 85bfbf764c..e6a3acfc90 100644 --- a/source/java/org/alfresco/repo/action/ActionImplTest.java +++ b/source/java/org/alfresco/repo/action/ActionImplTest.java @@ -41,9 +41,9 @@ public class ActionImplTest extends BaseParameterizedItemImplTest protected ParameterizedItemImpl create() { return new ActionImpl( + null, ID, NAME, - null, this.paramValues); } @@ -65,7 +65,7 @@ public class ActionImplTest extends BaseParameterizedItemImplTest action.setTitle("title"); action.setDescription("description"); action.setExecuteAsynchronously(true); - Action compensatingAction = new ActionImpl(GUID.generate(), "actionDefintionName", null); + Action compensatingAction = new ActionImpl(null, GUID.generate(), "actionDefintionName", null); action.setCompensatingAction(compensatingAction); // Check the values have been set diff --git a/source/java/org/alfresco/repo/action/ActionServiceImpl.java b/source/java/org/alfresco/repo/action/ActionServiceImpl.java index f7dad34e08..0ec6be6296 100644 --- a/source/java/org/alfresco/repo/action/ActionServiceImpl.java +++ b/source/java/org/alfresco/repo/action/ActionServiceImpl.java @@ -309,7 +309,7 @@ public class ActionServiceImpl implements ActionService, RuntimeActionService, A */ public Action createAction(String name) { - return new ActionImpl(GUID.generate(),name, null); + return new ActionImpl(null, GUID.generate(),name, null); } /** @@ -327,7 +327,7 @@ public class ActionServiceImpl implements ActionService, RuntimeActionService, A */ public CompositeAction createCompositeAction() { - return new CompositeActionImpl(GUID.generate(), null); + return new CompositeActionImpl(null, GUID.generate()); } /** @@ -594,42 +594,51 @@ public class ActionServiceImpl implements ActionService, RuntimeActionService, A // Apply the actionable aspect this.nodeService.addAspect(nodeRef, ActionModel.ASPECT_ACTIONS, null); } - - Map props = new HashMap(2); - props.put(ActionModel.PROP_DEFINITION_NAME, action.getActionDefinitionName()); - props.put(ContentModel.PROP_NODE_UUID, action.getId()); - - QName actionType = ActionModel.TYPE_ACTION; - if(action instanceof CompositeAction) - { - actionType = ActionModel.TYPE_COMPOSITE_ACTION; - } - - // Create the action node - actionNodeRef = this.nodeService.createNode( - getSavedActionFolderRef(nodeRef), - ContentModel.ASSOC_CONTAINS, - ASSOC_NAME_ACTIONS, - actionType, - props).getChildRef(); - - // Update the created details - ((ActionImpl)action).setCreator((String)this.nodeService.getProperty(actionNodeRef, ContentModel.PROP_CREATOR)); - ((ActionImpl)action).setCreatedDate((Date)this.nodeService.getProperty(actionNodeRef, ContentModel.PROP_CREATED)); + + // Create the action nod reference + actionNodeRef = createActionNodeRef(action, + getSavedActionFolderRef(nodeRef), + ContentModel.ASSOC_CONTAINS, + ASSOC_NAME_ACTIONS); } - saveActionImpl(nodeRef, actionNodeRef, action); + saveActionImpl(actionNodeRef, action); } + public NodeRef createActionNodeRef(Action action, NodeRef parentNodeRef, QName assocTypeName, QName assocName) + { + Map props = new HashMap(2); + props.put(ActionModel.PROP_DEFINITION_NAME, action.getActionDefinitionName()); + props.put(ContentModel.PROP_NODE_UUID, action.getId()); + + QName actionType = ActionModel.TYPE_ACTION; + if(action instanceof CompositeAction) + { + actionType = ActionModel.TYPE_COMPOSITE_ACTION; + } + + // Create the action node + NodeRef actionNodeRef = this.nodeService.createNode( + parentNodeRef, + assocTypeName, + assocName, + actionType, + props).getChildRef(); + + // Update the created details and the node reference + ((ActionImpl)action).setCreator((String)this.nodeService.getProperty(actionNodeRef, ContentModel.PROP_CREATOR)); + ((ActionImpl)action).setCreatedDate((Date)this.nodeService.getProperty(actionNodeRef, ContentModel.PROP_CREATED)); + ((ActionImpl)action).setNodeRef(actionNodeRef); + + return actionNodeRef; + } + /** * @see org.alfresco.repo.action.RuntimeActionService#saveActionImpl(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.action.Action) */ - public void saveActionImpl(NodeRef owningNodeRef, NodeRef actionNodeRef, Action action) + public void saveActionImpl(NodeRef actionNodeRef, Action action) { - // Set the owning node ref - ((ActionImpl)action).setOwningNodeRef(owningNodeRef); - - // Save action properties + // Save action properties saveActionProperties(actionNodeRef, action); // Update the parameters of the action @@ -671,18 +680,20 @@ public class ActionServiceImpl implements ActionService, RuntimeActionService, A { if (compensatingAction != null) { - Map props2 = new HashMap(2); - props2.put(ActionModel.PROP_DEFINITION_NAME, compensatingAction.getActionDefinitionName()); - props2.put(ContentModel.PROP_NODE_UUID, compensatingAction.getId()); + //Map props2 = new HashMap(2); + //props2.put(ActionModel.PROP_DEFINITION_NAME, compensatingAction.getActionDefinitionName()); + //props2.put(ContentModel.PROP_NODE_UUID, compensatingAction.getId()); - NodeRef compensatingActionNodeRef = this.nodeService.createNode( - actionNodeRef, - ActionModel.ASSOC_COMPENSATING_ACTION, - ActionModel.ASSOC_COMPENSATING_ACTION, - ActionModel.TYPE_ACTION, - props2).getChildRef(); - - saveActionImpl(compensatingAction.getOwningNodeRef(), compensatingActionNodeRef, compensatingAction); + //NodeRef compensatingActionNodeRef = this.nodeService.createNode( + /// actionNodeRef, + // ActionModel.ASSOC_COMPENSATING_ACTION, + // ActionModel.ASSOC_COMPENSATING_ACTION, + // ActionModel.TYPE_ACTION, + // props2).getChildRef(); + + // Create the compensating node reference + NodeRef compensatingActionNodeRef = createActionNodeRef(compensatingAction, actionNodeRef, ActionModel.ASSOC_COMPENSATING_ACTION, ActionModel.ASSOC_COMPENSATING_ACTION); + saveActionImpl(compensatingActionNodeRef, compensatingAction); } } else @@ -694,7 +705,7 @@ public class ActionServiceImpl implements ActionService, RuntimeActionService, A } else { - saveActionImpl(compensatingAction.getOwningNodeRef(), assoc.getChildRef(), compensatingAction); + saveActionImpl(assoc.getChildRef(), compensatingAction); } } } @@ -730,7 +741,7 @@ public class ActionServiceImpl implements ActionService, RuntimeActionService, A { // Update the action Action action = idToAction.get(actionNodeRef.getId()); - saveActionImpl(action.getOwningNodeRef(), actionNodeRef, action); + saveActionImpl(actionNodeRef, action); orderedIds.remove(actionNodeRef.getId()); } @@ -752,7 +763,7 @@ public class ActionServiceImpl implements ActionService, RuntimeActionService, A ActionModel.TYPE_ACTION, props).getChildRef(); - saveActionImpl(action.getOwningNodeRef(), actionNodeRef, action); + saveActionImpl(actionNodeRef, action); } } @@ -889,7 +900,7 @@ public class ActionServiceImpl implements ActionService, RuntimeActionService, A for (ChildAssociationRef action : actions) { NodeRef actionNodeRef = action.getChildRef(); - result.add(createAction(nodeRef, actionNodeRef)); + result.add(createAction(actionNodeRef)); } } @@ -902,7 +913,7 @@ public class ActionServiceImpl implements ActionService, RuntimeActionService, A * @param actionNodeRef the action node reference * @return the action */ - private Action createAction(NodeRef owningNodeRef, NodeRef actionNodeRef) + public Action createAction(NodeRef actionNodeRef) { Action result = null; @@ -912,13 +923,13 @@ public class ActionServiceImpl implements ActionService, RuntimeActionService, A if (ActionModel.TYPE_COMPOSITE_ACTION.equals(actionType) == true) { // Create a composite action - result = new CompositeActionImpl(actionNodeRef.getId(), owningNodeRef); + result = new CompositeActionImpl(actionNodeRef, actionNodeRef.getId()); populateCompositeAction(actionNodeRef, (CompositeAction)result); } else { // Create an action - result = new ActionImpl(actionNodeRef.getId(), (String)properties.get(ActionModel.PROP_DEFINITION_NAME), owningNodeRef); + result = new ActionImpl(actionNodeRef, actionNodeRef.getId(), (String)properties.get(ActionModel.PROP_DEFINITION_NAME)); populateAction(actionNodeRef, result); } @@ -978,7 +989,7 @@ public class ActionServiceImpl implements ActionService, RuntimeActionService, A List assocs = this.nodeService.getChildAssocs(actionNodeRef, RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_COMPENSATING_ACTION); if (assocs.size() != 0) { - Action compensatingAction = createAction(action.getOwningNodeRef(), assocs.get(0).getChildRef()); + Action compensatingAction = createAction(assocs.get(0).getChildRef()); action.setCompensatingAction(compensatingAction); } } @@ -1039,7 +1050,7 @@ public class ActionServiceImpl implements ActionService, RuntimeActionService, A for (ChildAssociationRef action : actions) { NodeRef actionNodeRef = action.getChildRef(); - compositeAction.addAction(createAction(compositeAction.getOwningNodeRef(), actionNodeRef)); + compositeAction.addAction(createAction(actionNodeRef)); } } @@ -1056,7 +1067,7 @@ public class ActionServiceImpl implements ActionService, RuntimeActionService, A NodeRef actionNodeRef = getActionNodeRefFromId(nodeRef, actionId); if (actionNodeRef != null) { - result = createAction(nodeRef, actionNodeRef); + result = createAction(actionNodeRef); } } diff --git a/source/java/org/alfresco/repo/action/ActionServiceImplTest.java b/source/java/org/alfresco/repo/action/ActionServiceImplTest.java index 6af845cda7..fda3864dc0 100644 --- a/source/java/org/alfresco/repo/action/ActionServiceImplTest.java +++ b/source/java/org/alfresco/repo/action/ActionServiceImplTest.java @@ -330,7 +330,6 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest // Edit the properties of the action Map properties = new HashMap(1); properties.put(ContentModel.PROP_NAME, "testName"); - action.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_PROPERTIES, (Serializable)properties); action.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_AUDITABLE); // Set the compensating action @@ -342,12 +341,8 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest Action savedAction2 = this.actionService.getAction(this.nodeRef, actionId); // Check the updated properties - assertEquals(2, savedAction2.getParameterValues().size()); + assertEquals(1, savedAction2.getParameterValues().size()); assertEquals(ContentModel.ASPECT_AUDITABLE, savedAction2.getParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME)); - Map temp = (Map)savedAction2.getParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_PROPERTIES); - assertNotNull(temp); - assertEquals(1, temp.size()); - assertEquals("testName", temp.get(ContentModel.PROP_NAME)); // Check the compensating action Action savedCompensatingAction = savedAction2.getCompensatingAction(); @@ -388,19 +383,13 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest action.setExecuteAsynchronously(true); // Check the owning node ref - assertNull(action.getOwningNodeRef()); + //assertNull(action.getOwningNodeRef()); // Save the action this.actionService.saveAction(this.nodeRef, action); - // Check the owning node ref - assertEquals(this.nodeRef, action.getOwningNodeRef()); - // Get the action - Action savedAction = this.actionService.getAction(this.nodeRef, actionId); - - // Check the owning node ref - assertEquals(this.nodeRef, savedAction.getOwningNodeRef());; + this.actionService.getAction(this.nodeRef, actionId); } /** @@ -414,9 +403,6 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest // Set the parameters of the action action.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_VERSIONABLE); - Map properties = new HashMap(1); - properties.put(ContentModel.PROP_NAME, "testName"); - action.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_PROPERTIES, (Serializable)properties); // Set the conditions of the action ActionCondition actionCondition = this.actionService.createActionCondition(NoConditionEvaluator.NAME); @@ -439,10 +425,6 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest // Check the properties assertEquals(action.getParameterValues().size(), savedAction.getParameterValues().size()); assertEquals(ContentModel.ASPECT_VERSIONABLE, savedAction.getParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME)); - Map temp = (Map)savedAction.getParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_PROPERTIES); - assertNotNull(temp); - assertEquals(1, temp.size()); - assertEquals("testName", temp.get(ContentModel.PROP_NAME)); // Check the conditions assertNotNull(savedAction.getActionConditions()); diff --git a/source/java/org/alfresco/repo/action/AsynchronousActionExecutionQueueImpl.java b/source/java/org/alfresco/repo/action/AsynchronousActionExecutionQueueImpl.java index aa49b4b72a..759ff5f72f 100644 --- a/source/java/org/alfresco/repo/action/AsynchronousActionExecutionQueueImpl.java +++ b/source/java/org/alfresco/repo/action/AsynchronousActionExecutionQueueImpl.java @@ -21,7 +21,9 @@ import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import org.alfresco.repo.rule.RuleServiceImpl; import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.TransactionUtil; import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.ActionServiceException; @@ -105,11 +107,14 @@ public class AsynchronousActionExecutionQueueImpl extends ThreadPoolExecutor imp * org.alfresco.service.cmr.action.Action, boolean, * org.alfresco.service.cmr.repository.NodeRef) */ - public void executeAction(RuntimeActionService actionService, Action action, NodeRef actionedUponNodeRef, + @SuppressWarnings("unchecked") + public void executeAction(RuntimeActionService actionService, Action action, NodeRef actionedUponNodeRef, boolean checkConditions, Set actionChain, NodeRef actionExecutionHistoryNodeRef) { + Set executedRules = + (Set) AlfrescoTransactionSupport.getResource("RuleServiceImpl.ExecutedRules"); execute(new ActionExecutionWrapper(actionService, transactionService, authenticationComponent, action, - actionedUponNodeRef, checkConditions, actionExecutionHistoryNodeRef, actionChain)); + actionedUponNodeRef, checkConditions, actionExecutionHistoryNodeRef, actionChain, executedRules)); } /** @@ -176,6 +181,11 @@ public class AsynchronousActionExecutionQueueImpl extends ThreadPoolExecutor imp * The action chain */ private Set actionChain; + + /** + * List of executed list, helps to prevent loop scenarios with async rules + */ + private Set executedRules; /** * Constructor @@ -190,7 +200,7 @@ public class AsynchronousActionExecutionQueueImpl extends ThreadPoolExecutor imp */ public ActionExecutionWrapper(RuntimeActionService actionService, TransactionService transactionService, AuthenticationComponent authenticationComponent, Action action, NodeRef actionedUponNodeRef, - boolean checkConditions, NodeRef actionExecutionHistoryNodeRef, Set actionChain) + boolean checkConditions, NodeRef actionExecutionHistoryNodeRef, Set actionChain, Set executedRules) { this.actionService = actionService; this.transactionService = transactionService; @@ -200,6 +210,7 @@ public class AsynchronousActionExecutionQueueImpl extends ThreadPoolExecutor imp this.checkConditions = checkConditions; this.actionExecutionHistoryNodeRef = actionExecutionHistoryNodeRef; this.actionChain = actionChain; + this.executedRules = executedRules; } /** @@ -276,7 +287,12 @@ public class AsynchronousActionExecutionQueueImpl extends ThreadPoolExecutor imp new TransactionUtil.TransactionWork() { public Object doWork() - { + { + if (ActionExecutionWrapper.this.executedRules != null) + { + AlfrescoTransactionSupport.bindResource("RuleServiceImpl.ExecutedRules", ActionExecutionWrapper.this.executedRules); + } + ActionExecutionWrapper.this.actionService.executeActionImpl( ActionExecutionWrapper.this.action, ActionExecutionWrapper.this.actionedUponNodeRef, diff --git a/source/java/org/alfresco/repo/action/CompositeActionImpl.java b/source/java/org/alfresco/repo/action/CompositeActionImpl.java index a761c9a9ad..55861c810c 100644 --- a/source/java/org/alfresco/repo/action/CompositeActionImpl.java +++ b/source/java/org/alfresco/repo/action/CompositeActionImpl.java @@ -46,9 +46,9 @@ public class CompositeActionImpl extends ActionImpl implements CompositeAction * * @param id the action id */ - public CompositeActionImpl(String id, NodeRef owningNodeRef) + public CompositeActionImpl(NodeRef nodeRef, String id) { - super(id, CompositeActionExecuter.NAME, owningNodeRef); + super(nodeRef, id, CompositeActionExecuter.NAME); } /** diff --git a/source/java/org/alfresco/repo/action/CompositeActionImplTest.java b/source/java/org/alfresco/repo/action/CompositeActionImplTest.java index 6d044a79e8..f09f5b5810 100644 --- a/source/java/org/alfresco/repo/action/CompositeActionImplTest.java +++ b/source/java/org/alfresco/repo/action/CompositeActionImplTest.java @@ -37,11 +37,11 @@ public class CompositeActionImplTest extends ActionImplTest public void testActions() { - Action action1 = new ActionImpl(ACTION1_ID, ACTION1_NAME, null); - Action action2 = new ActionImpl(ACTION2_ID, ACTION2_NAME, null); - Action action3 = new ActionImpl(ACTION3_ID, ACTION3_NAME, null); + Action action1 = new ActionImpl(null, ACTION1_ID, ACTION1_NAME, null); + Action action2 = new ActionImpl(null, ACTION2_ID, ACTION2_NAME, null); + Action action3 = new ActionImpl(null, ACTION3_ID, ACTION3_NAME, null); - CompositeAction compositeAction = new CompositeActionImpl(ID, null); + CompositeAction compositeAction = new CompositeActionImpl(null, ID); // Check has no action assertFalse(compositeAction.hasActions()); diff --git a/source/java/org/alfresco/repo/action/ParameterDefinitionImpl.java b/source/java/org/alfresco/repo/action/ParameterDefinitionImpl.java index 99e0f57556..51c6f27be1 100644 --- a/source/java/org/alfresco/repo/action/ParameterDefinitionImpl.java +++ b/source/java/org/alfresco/repo/action/ParameterDefinitionImpl.java @@ -43,6 +43,11 @@ public class ParameterDefinitionImpl implements ParameterDefinition, Serializabl */ private QName type; + /** + * Is this a multi-valued parameter? + */ + private boolean isMultiValued; + /** * The display label */ @@ -70,8 +75,30 @@ public class ParameterDefinitionImpl implements ParameterDefinition, Serializabl this.type = type; this.displayLabel = displayLabel; this.isMandatory = isMandatory; + this.isMultiValued = false; } - + + /** + * Constructor + * + * @param name the name of the parameter + * @param type the type of the parameter + * @param displayLabel the display label + */ + public ParameterDefinitionImpl( + String name, + QName type, + boolean isMandatory, + String displayLabel, + boolean isMultiValued) + { + this.name = name; + this.type = type; + this.displayLabel = displayLabel; + this.isMandatory = isMandatory; + this.isMultiValued = isMultiValued; + } + /** * @see org.alfresco.service.cmr.action.ParameterDefinition#getName() */ @@ -96,6 +123,14 @@ public class ParameterDefinitionImpl implements ParameterDefinition, Serializabl return this.isMandatory; } + /** + * @see org.alfresco.service.cmr.action.ParameterDefinition#isMultiValued() + */ + public boolean isMultiValued() + { + return this.isMultiValued; + } + /** * @see org.alfresco.service.cmr.action.ParameterDefinition#getDisplayLabel() */ diff --git a/source/java/org/alfresco/repo/action/RuntimeActionService.java b/source/java/org/alfresco/repo/action/RuntimeActionService.java index 0ba0c5bdac..a69bb1462e 100644 --- a/source/java/org/alfresco/repo/action/RuntimeActionService.java +++ b/source/java/org/alfresco/repo/action/RuntimeActionService.java @@ -23,21 +23,38 @@ import org.alfresco.repo.action.ActionServiceImpl.PendingAction; import org.alfresco.repo.action.evaluator.ActionConditionEvaluator; import org.alfresco.repo.action.executer.ActionExecuter; import org.alfresco.service.cmr.action.Action; -import org.alfresco.service.cmr.action.CompositeAction; import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; /** * @author Roy Wetherall */ public interface RuntimeActionService { + /** + * Get the asynchronous action queue. + * + * @return the asynchronous action queue + */ AsynchronousActionExecutionQueue getAsynchronousActionExecutionQueue(); + /** + * Register an action condition evaluator + * + * @param actionConditionEvaluator action condition evaluator + */ void registerActionConditionEvaluator(ActionConditionEvaluator actionConditionEvaluator); + /** + * Register an action executer + * + * @param actionExecuter action executer + */ void registerActionExecuter(ActionExecuter actionExecuter); - - void populateCompositeAction(NodeRef compositeNodeRef, CompositeAction compositeAction); + + Action createAction(NodeRef actionNodeRef); + + NodeRef createActionNodeRef(Action action, NodeRef parentNodeRef, QName assocTypeName, QName assocName); /** * Save action, used internally to store the details of an action on the aciton node. @@ -45,7 +62,7 @@ public interface RuntimeActionService * @param actionNodeRef the action node reference * @param action the action */ - void saveActionImpl(NodeRef owningNodeRef, NodeRef actionNodeRef, Action action); + void saveActionImpl(NodeRef actionNodeRef, Action action); /** * @@ -60,7 +77,18 @@ public interface RuntimeActionService boolean executedAsynchronously, Set actionChain); + /** + * Execute an action directly + * + * @param action the action + * @param actionedUponNodeRef the actioned upon node reference + */ public void directActionExecution(Action action, NodeRef actionedUponNodeRef); + /** + * Gets a list of the actions that are pending post transaction + * + * @return list of pending actions + */ public List getPostTransactionPendingActions(); } diff --git a/source/java/org/alfresco/repo/action/executer/AddFeaturesActionExecuter.java b/source/java/org/alfresco/repo/action/executer/AddFeaturesActionExecuter.java index 13a8368083..73c6976f9c 100644 --- a/source/java/org/alfresco/repo/action/executer/AddFeaturesActionExecuter.java +++ b/source/java/org/alfresco/repo/action/executer/AddFeaturesActionExecuter.java @@ -41,7 +41,6 @@ public class AddFeaturesActionExecuter extends ActionExecuterAbstractBase */ public static final String NAME = "add-features"; public static final String PARAM_ASPECT_NAME = "aspect-name"; - public static final String PARAM_ASPECT_PROPERTIES = "aspect_properties"; /** * The node service diff --git a/source/java/org/alfresco/repo/action/executer/AddFeaturesActionExecuterTest.java b/source/java/org/alfresco/repo/action/executer/AddFeaturesActionExecuterTest.java index 7e508183cd..935ca4bc73 100644 --- a/source/java/org/alfresco/repo/action/executer/AddFeaturesActionExecuterTest.java +++ b/source/java/org/alfresco/repo/action/executer/AddFeaturesActionExecuterTest.java @@ -100,7 +100,7 @@ public class AddFeaturesActionExecuterTest extends BaseSpringTest assertFalse(this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_CLASSIFIABLE)); // Execute the action - ActionImpl action = new ActionImpl(ID, AddFeaturesActionExecuter.NAME, null); + ActionImpl action = new ActionImpl(null, ID, AddFeaturesActionExecuter.NAME, null); action.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_CLASSIFIABLE); this.executer.execute(action, this.nodeRef); diff --git a/source/java/org/alfresco/repo/action/executer/ContentMetadataExtracter.java b/source/java/org/alfresco/repo/action/executer/ContentMetadataExtracter.java index 66d29848e4..04118eebb8 100644 --- a/source/java/org/alfresco/repo/action/executer/ContentMetadataExtracter.java +++ b/source/java/org/alfresco/repo/action/executer/ContentMetadataExtracter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005 Jesper Steen Møller + * Copyright (C) 2005 Jesper Steen Møller * * Licensed under the Mozilla Public License version 1.1 * with a permitted attribution clause. You may obtain a @@ -43,7 +43,7 @@ import org.alfresco.service.namespace.QName; * otherwise they are left as is.
* This may change if the action gets parameterized in future. * - * @author Jesper Steen Møller + * @author Jesper Steen Møller */ public class ContentMetadataExtracter extends ActionExecuterAbstractBase { diff --git a/source/java/org/alfresco/repo/action/executer/ContentMetadataExtracterTest.java b/source/java/org/alfresco/repo/action/executer/ContentMetadataExtracterTest.java index 6f5c500ebf..0d50b5c7c5 100644 --- a/source/java/org/alfresco/repo/action/executer/ContentMetadataExtracterTest.java +++ b/source/java/org/alfresco/repo/action/executer/ContentMetadataExtracterTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005 Jesper Steen Møller + * Copyright (C) 2005 Jesper Steen M�ller * * Licensed under the Mozilla Public License version 1.1 * with a permitted attribution clause. You may obtain a @@ -37,7 +37,7 @@ import org.alfresco.util.GUID; * Test of the ActionExecuter for extracting metadata. Note: This test makes * assumptions about the PDF test data for PdfBoxExtracter. * - * @author Jesper Steen Møller + * @author Jesper Steen M�ller */ public class ContentMetadataExtracterTest extends BaseSpringTest { @@ -102,7 +102,7 @@ public class ContentMetadataExtracterTest extends BaseSpringTest this.nodeService.setProperties(this.nodeRef, props); // Execute the action - ActionImpl action = new ActionImpl(ID, SetPropertyValueActionExecuter.NAME, null); + ActionImpl action = new ActionImpl(null, ID, SetPropertyValueActionExecuter.NAME, null); this.executer.execute(action, this.nodeRef); @@ -130,7 +130,7 @@ public class ContentMetadataExtracterTest extends BaseSpringTest this.nodeService.setProperties(this.nodeRef, props); // Execute the action - ActionImpl action = new ActionImpl(ID, SetPropertyValueActionExecuter.NAME, null); + ActionImpl action = new ActionImpl(null, ID, SetPropertyValueActionExecuter.NAME, null); this.executer.execute(action, this.nodeRef); diff --git a/source/java/org/alfresco/repo/action/executer/ExecuteAllRulesActionExecuter.java b/source/java/org/alfresco/repo/action/executer/ExecuteAllRulesActionExecuter.java index 2d9b0eb779..934b687be9 100644 --- a/source/java/org/alfresco/repo/action/executer/ExecuteAllRulesActionExecuter.java +++ b/source/java/org/alfresco/repo/action/executer/ExecuteAllRulesActionExecuter.java @@ -133,8 +133,11 @@ public class ExecuteAllRulesActionExecuter extends ActionExecuterAbstractBase { for (Rule rule : rules) { - // Apply the rule to the child node - this.actionService.executeAction(rule, child); + Action action = rule.getAction(); + if (action != null) + { + this.actionService.executeAction(action, child); + } } } } diff --git a/source/java/org/alfresco/repo/action/executer/ExecuteAllRulesActionExecuterTest.java b/source/java/org/alfresco/repo/action/executer/ExecuteAllRulesActionExecuterTest.java index 5b82fe00ab..aeccbf1d72 100644 --- a/source/java/org/alfresco/repo/action/executer/ExecuteAllRulesActionExecuterTest.java +++ b/source/java/org/alfresco/repo/action/executer/ExecuteAllRulesActionExecuterTest.java @@ -105,15 +105,18 @@ public class ExecuteAllRulesActionExecuterTest extends BaseSpringTest ContentModel.TYPE_CONTENT).getChildRef(); // Add a couple of rules to the folder - Rule rule1 = this.ruleService.createRule(RuleType.INBOUND); + Rule rule1 = new Rule(); + rule1.setRuleType(RuleType.INBOUND); Action action1 = this.actionService.createAction(AddFeaturesActionExecuter.NAME); action1.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_VERSIONABLE); - rule1.addAction(action1); + rule1.setAction(action1); this.ruleService.saveRule(folder, rule1); - Rule rule2 = this.ruleService.createRule(RuleType.INBOUND); + + Rule rule2 = new Rule(); + rule2.setRuleType(RuleType.INBOUND); Action action2 = this.actionService.createAction(AddFeaturesActionExecuter.NAME); action2.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_CLASSIFIABLE); - rule2.addAction(action2); + rule2.setAction(action2); this.ruleService.saveRule(folder, rule2); // Check the the docs don't have the aspects yet @@ -125,7 +128,7 @@ public class ExecuteAllRulesActionExecuterTest extends BaseSpringTest assertTrue(this.nodeService.exists(folder)); // Execute the action - ActionImpl action = new ActionImpl(ID, ExecuteAllRulesActionExecuter.NAME, null); + ActionImpl action = new ActionImpl(null, ID, ExecuteAllRulesActionExecuter.NAME, null); this.executer.execute(action, folder); assertTrue(this.nodeService.hasAspect(doc1, ContentModel.ASPECT_VERSIONABLE)); diff --git a/source/java/org/alfresco/repo/action/executer/MailActionExecuter.java b/source/java/org/alfresco/repo/action/executer/MailActionExecuter.java index 0bbc5b18b9..c256f07916 100644 --- a/source/java/org/alfresco/repo/action/executer/MailActionExecuter.java +++ b/source/java/org/alfresco/repo/action/executer/MailActionExecuter.java @@ -216,7 +216,7 @@ public class MailActionExecuter extends ActionExecuterAbstractBase { if (personService.personExists(userAuth) == true) { - NodeRef person = personService.getPerson(authority); + NodeRef person = personService.getPerson(userAuth); String address = (String)nodeService.getProperty(person, ContentModel.PROP_EMAIL); if (address != null && address.length() != 0) { @@ -311,7 +311,7 @@ public class MailActionExecuter extends ActionExecuterAbstractBase protected void addParameterDefinitions(List paramList) { paramList.add(new ParameterDefinitionImpl(PARAM_TO, DataTypeDefinition.TEXT, false, getParamDisplayLabel(PARAM_TO))); - paramList.add(new ParameterDefinitionImpl(PARAM_TO_MANY, DataTypeDefinition.ANY, false, getParamDisplayLabel(PARAM_TO_MANY))); + paramList.add(new ParameterDefinitionImpl(PARAM_TO_MANY, DataTypeDefinition.TEXT, false, getParamDisplayLabel(PARAM_TO_MANY), true)); paramList.add(new ParameterDefinitionImpl(PARAM_SUBJECT, DataTypeDefinition.TEXT, true, getParamDisplayLabel(PARAM_SUBJECT))); paramList.add(new ParameterDefinitionImpl(PARAM_TEXT, DataTypeDefinition.TEXT, true, getParamDisplayLabel(PARAM_TEXT))); paramList.add(new ParameterDefinitionImpl(PARAM_FROM, DataTypeDefinition.TEXT, false, getParamDisplayLabel(PARAM_FROM))); diff --git a/source/java/org/alfresco/repo/action/executer/RemoveFeaturesActionExecuterTest.java b/source/java/org/alfresco/repo/action/executer/RemoveFeaturesActionExecuterTest.java index 0d7ce950e0..c80a9b8e08 100644 --- a/source/java/org/alfresco/repo/action/executer/RemoveFeaturesActionExecuterTest.java +++ b/source/java/org/alfresco/repo/action/executer/RemoveFeaturesActionExecuterTest.java @@ -101,7 +101,7 @@ public class RemoveFeaturesActionExecuterTest extends BaseSpringTest assertTrue(this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_CLASSIFIABLE)); // Execute the action - ActionImpl action = new ActionImpl(ID, RemoveFeaturesActionExecuter.NAME, null); + ActionImpl action = new ActionImpl(null, ID, RemoveFeaturesActionExecuter.NAME, null); action.setParameterValue(RemoveFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_CLASSIFIABLE); this.executer.execute(action, this.nodeRef); @@ -109,7 +109,7 @@ public class RemoveFeaturesActionExecuterTest extends BaseSpringTest assertFalse(this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_CLASSIFIABLE)); // Now try and remove an aspect that is not present - ActionImpl action2 = new ActionImpl(ID, RemoveFeaturesActionExecuter.NAME, null); + ActionImpl action2 = new ActionImpl(null, ID, RemoveFeaturesActionExecuter.NAME, null); action2.setParameterValue(RemoveFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_VERSIONABLE); this.executer.execute(action2, this.nodeRef); } diff --git a/source/java/org/alfresco/repo/action/executer/ScriptActionExecutor.java b/source/java/org/alfresco/repo/action/executer/ScriptActionExecutor.java index e9ba2b2363..1815acecd3 100644 --- a/source/java/org/alfresco/repo/action/executer/ScriptActionExecutor.java +++ b/source/java/org/alfresco/repo/action/executer/ScriptActionExecutor.java @@ -38,7 +38,6 @@ public class ScriptActionExecutor extends ActionExecuterAbstractBase { public static final String NAME = "script"; public static final String PARAM_SCRIPTREF = "script-ref"; - public static final String PARAM_SPACEREF = "space-ref"; private ServiceRegistry serviceRegistry; private PersonService personService; @@ -80,12 +79,7 @@ public class ScriptActionExecutor extends ActionExecuterAbstractBase if (nodeService.exists(actionedUponNodeRef)) { NodeRef scriptRef = (NodeRef)action.getParameterValue(PARAM_SCRIPTREF); - NodeRef spaceRef = (NodeRef)action.getParameterValue(PARAM_SPACEREF); - if (spaceRef == null) - { - // get primary parent of the doc as no space has been specified - spaceRef = nodeService.getPrimaryParent(actionedUponNodeRef).getParentRef(); - } + NodeRef spaceRef = this.serviceRegistry.getRuleService().getOwningNodeRef(action); if (nodeService.exists(scriptRef)) { @@ -120,7 +114,6 @@ public class ScriptActionExecutor extends ActionExecuterAbstractBase protected void addParameterDefinitions(List paramList) { paramList.add(new ParameterDefinitionImpl(PARAM_SCRIPTREF, DataTypeDefinition.NODE_REF, true, getParamDisplayLabel(PARAM_SCRIPTREF))); - paramList.add(new ParameterDefinitionImpl(PARAM_SPACEREF, DataTypeDefinition.NODE_REF, false, getParamDisplayLabel(PARAM_SPACEREF))); } private NodeRef getCompanyHome() diff --git a/source/java/org/alfresco/repo/action/executer/SetPropertyValueActionExecuterTest.java b/source/java/org/alfresco/repo/action/executer/SetPropertyValueActionExecuterTest.java index 01e835a274..3e64439881 100644 --- a/source/java/org/alfresco/repo/action/executer/SetPropertyValueActionExecuterTest.java +++ b/source/java/org/alfresco/repo/action/executer/SetPropertyValueActionExecuterTest.java @@ -74,7 +74,7 @@ public class SetPropertyValueActionExecuterTest extends BaseSpringTest public void testExecution() { // Execute the action - ActionImpl action = new ActionImpl(ID, SetPropertyValueActionExecuter.NAME, null); + ActionImpl action = new ActionImpl(null, ID, SetPropertyValueActionExecuter.NAME, null); action.setParameterValue(SetPropertyValueActionExecuter.PARAM_PROPERTY, ContentModel.PROP_NAME); action.setParameterValue(SetPropertyValueActionExecuter.PARAM_VALUE, TEST_VALUE); this.executer.execute(action, this.nodeRef); diff --git a/source/java/org/alfresco/repo/action/executer/SpecialiseTypeActionExecuterTest.java b/source/java/org/alfresco/repo/action/executer/SpecialiseTypeActionExecuterTest.java index 101ef05f20..35b545b1ed 100644 --- a/source/java/org/alfresco/repo/action/executer/SpecialiseTypeActionExecuterTest.java +++ b/source/java/org/alfresco/repo/action/executer/SpecialiseTypeActionExecuterTest.java @@ -73,7 +73,7 @@ public class SpecialiseTypeActionExecuterTest extends BaseAlfrescoSpringTest assertEquals(ContentModel.TYPE_CONTENT, this.nodeService.getType(this.nodeRef)); // Execute the action - ActionImpl action = new ActionImpl(ID, SpecialiseTypeActionExecuter.NAME, null); + ActionImpl action = new ActionImpl(null, ID, SpecialiseTypeActionExecuter.NAME, null); action.setParameterValue(SpecialiseTypeActionExecuter.PARAM_TYPE_NAME, ContentModel.TYPE_FOLDER); this.executer.execute(action, this.nodeRef); diff --git a/source/java/org/alfresco/repo/action/executer/TransformActionExecuter.java b/source/java/org/alfresco/repo/action/executer/TransformActionExecuter.java index 4ddf81e03d..89c040ca15 100644 --- a/source/java/org/alfresco/repo/action/executer/TransformActionExecuter.java +++ b/source/java/org/alfresco/repo/action/executer/TransformActionExecuter.java @@ -169,7 +169,7 @@ public class TransformActionExecuter extends ActionExecuterAbstractBase // Calculate the destination name String originalName = (String)nodeService.getProperty(actionedUponNodeRef, ContentModel.PROP_NAME); - String newName = transformName(originalName, mimeType); + String newName = transformName(this.mimetypeService, originalName, mimeType); // Since we are overwriting we need to figure out whether the destination node exists NodeRef copyNodeRef = null; @@ -225,7 +225,7 @@ public class TransformActionExecuter extends ActionExecuterAbstractBase String originalTitle = (String)nodeService.getProperty(actionedUponNodeRef, ContentModel.PROP_TITLE); if (originalTitle != null && originalTitle.length() > 0) { - String newTitle = transformName(originalTitle, mimeType); + String newTitle = transformName(this.mimetypeService, originalTitle, mimeType); nodeService.setProperty(copyNodeRef, ContentModel.PROP_TITLE, newTitle); } } @@ -267,11 +267,11 @@ public class TransformActionExecuter extends ActionExecuterAbstractBase * Transform name from original extension to new extension * * @param original - * @param originalMimetype * @param newMimetype - * @return + * + * @return name with new extension as appropriate for the mimetype */ - private String transformName(String original, String newMimetype) + public static String transformName(MimetypeService mimetypeService, String original, String newMimetype) { // get the current extension int dotIndex = original.lastIndexOf('.'); diff --git a/source/java/org/alfresco/repo/admin/patch/PatchServiceImpl.java b/source/java/org/alfresco/repo/admin/patch/PatchServiceImpl.java index 0cae0f3a6c..3eba4c49fd 100644 --- a/source/java/org/alfresco/repo/admin/patch/PatchServiceImpl.java +++ b/source/java/org/alfresco/repo/admin/patch/PatchServiceImpl.java @@ -25,6 +25,7 @@ import java.util.Map; import org.alfresco.i18n.I18NUtil; import org.alfresco.repo.domain.AppliedPatch; import org.alfresco.service.cmr.admin.PatchException; +import org.alfresco.service.cmr.rule.RuleService; import org.alfresco.service.descriptor.Descriptor; import org.alfresco.service.descriptor.DescriptorService; import org.apache.commons.logging.Log; @@ -50,6 +51,7 @@ public class PatchServiceImpl implements PatchService private static Log logger = LogFactory.getLog(PatchServiceImpl.class); private DescriptorService descriptorService; + private RuleService ruleService; private PatchDaoService patchDaoService; private List patches; @@ -67,6 +69,11 @@ public class PatchServiceImpl implements PatchService { this.patchDaoService = patchDaoService; } + + public void setRuleService(RuleService ruleService) + { + this.ruleService = ruleService; + } public void registerPatch(Patch patch) { @@ -75,31 +82,50 @@ public class PatchServiceImpl implements PatchService public boolean applyOutstandingPatches() { - // construct a map of all known patches by ID - Map allPatchesById = new HashMap(23); - for (Patch patch : patches) - { - allPatchesById.put(patch.getId(), patch); - } - // construct a list of executed patches by ID - Map appliedPatchesById = new HashMap(23); - List appliedPatches = patchDaoService.getAppliedPatches(); - for (AppliedPatch appliedPatch : appliedPatches) - { - appliedPatchesById.put(appliedPatch.getId(), appliedPatch); - } - // go through all the patches and apply them where necessary boolean success = true; - for (Patch patch : allPatchesById.values()) + + try { - // apply the patch - success = applyPatchAndDependencies(patch, appliedPatchesById); - if (!success) + // Diable rules whilst processing the patches + this.ruleService.disableRules(); + try + { + // construct a map of all known patches by ID + Map allPatchesById = new HashMap(23); + for (Patch patch : patches) { - // we failed to apply a patch or one of its dependencies - terminate - break; + allPatchesById.put(patch.getId(), patch); } + // construct a list of executed patches by ID + Map appliedPatchesById = new HashMap(23); + List appliedPatches = patchDaoService.getAppliedPatches(); + for (AppliedPatch appliedPatch : appliedPatches) + { + appliedPatchesById.put(appliedPatch.getId(), appliedPatch); + } + + // go through all the patches and apply them where necessary + for (Patch patch : allPatchesById.values()) + { + // apply the patch + success = applyPatchAndDependencies(patch, appliedPatchesById); + if (!success) + { + // we failed to apply a patch or one of its dependencies - terminate + break; + } + } } + finally + { + this.ruleService.enableRules(); + } + } + catch (Throwable exception) + { + exception.printStackTrace(); + } + // done return success; } diff --git a/source/java/org/alfresco/repo/admin/patch/impl/ActionRuleDecouplingPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/ActionRuleDecouplingPatch.java new file mode 100644 index 0000000000..8f4aab96c8 --- /dev/null +++ b/source/java/org/alfresco/repo/admin/patch/impl/ActionRuleDecouplingPatch.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.admin.patch.impl; + +import java.io.Serializable; +import java.util.Map; + +import org.alfresco.i18n.I18NUtil; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.action.ActionModel; +import org.alfresco.repo.admin.patch.AbstractPatch; +import org.alfresco.repo.rule.RuleModel; +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.ResultSet; +import org.alfresco.service.namespace.QName; + +/** + * Patch to apply the model changes made when decoupling actions from rules. + * + * @author Roy Wetherall + */ +public class ActionRuleDecouplingPatch extends AbstractPatch +{ + private static final String MSG_RESULT = "patch.actionRuleDecouplingPatch.result"; + + /** + * @see org.alfresco.repo.admin.patch.AbstractPatch#applyInternal() + */ + @Override + protected String applyInternal() throws Exception + { + // Get a reference to the spaces store + StoreRef storeRef = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore"); + + // Get all the node's of type rule in the store + int updateCount = 0; + ResultSet resultSet = this.searchService.query(storeRef, "lucene", "TYPE:\"" + RuleModel.TYPE_RULE + "\""); + for (NodeRef origRuleNodeRef : resultSet.getNodeRefs()) + { + // Check that this rule need updated + Map origProperties = this.nodeService.getProperties(origRuleNodeRef); + if (origProperties.containsKey(RuleModel.PROP_EXECUTE_ASYNC) == false) + { + // 1) Change the type of the rule to be a composite action + this.nodeService.setType(origRuleNodeRef, ActionModel.TYPE_COMPOSITE_ACTION); + + // 2) Create a new rule node + ChildAssociationRef parentRef = this.nodeService.getPrimaryParent(origRuleNodeRef); + NodeRef newRuleNodeRef = this.nodeService.createNode( + parentRef.getParentRef(), + parentRef.getTypeQName(), + parentRef.getQName(), + RuleModel.TYPE_RULE).getChildRef(); + + // 3) Move the origional rule under the new rule + this.nodeService.moveNode( + origRuleNodeRef, + newRuleNodeRef, + RuleModel.ASSOC_ACTION, + RuleModel.ASSOC_ACTION); + + // 4) Move the various properties from the origional, onto the new rule + Map newProperties = this.nodeService.getProperties(newRuleNodeRef); + + // Set the rule type, execute async and applyToChildren properties on the rule + String ruleType = (String)origProperties.get(RuleModel.PROP_RULE_TYPE); + origProperties.remove(RuleModel.PROP_RULE_TYPE); + newProperties.put(RuleModel.PROP_RULE_TYPE, ruleType); + Boolean executeAsync = (Boolean)origProperties.get(ActionModel.PROP_EXECUTE_ASYNCHRONOUSLY); + origProperties.remove(ActionModel.PROP_EXECUTE_ASYNCHRONOUSLY); + newProperties.put(RuleModel.PROP_EXECUTE_ASYNC, executeAsync); + Boolean applyToChildren = (Boolean)origProperties.get(RuleModel.PROP_APPLY_TO_CHILDREN); + origProperties.remove(RuleModel.PROP_APPLY_TO_CHILDREN); + newProperties.put(RuleModel.PROP_APPLY_TO_CHILDREN, applyToChildren); + origProperties.remove(QName.createQName(RuleModel.RULE_MODEL_URI, "owningNodeRef")); + + // Move the action and description values from the composite action onto the rule + String title = (String)origProperties.get(ActionModel.PROP_ACTION_TITLE); + origProperties.remove(ActionModel.PROP_ACTION_TITLE); + String description = (String)origProperties.get(ActionModel.PROP_ACTION_DESCRIPTION); + origProperties.remove(ActionModel.PROP_ACTION_DESCRIPTION); + newProperties.put(ContentModel.PROP_TITLE, title); + newProperties.put(ContentModel.PROP_DESCRIPTION, description); + + // Set the updated property values + this.nodeService.setProperties(origRuleNodeRef, origProperties); + this.nodeService.setProperties(newRuleNodeRef, newProperties); + + // Increment the update count + updateCount++; + } + } + + // Done + String msg = I18NUtil.getMessage(MSG_RESULT, updateCount); + return msg; + } + +} diff --git a/source/java/org/alfresco/repo/admin/patch/impl/GuestPersonPermissionPatch2.java b/source/java/org/alfresco/repo/admin/patch/impl/GuestPersonPermissionPatch2.java new file mode 100644 index 0000000000..64892ea29c --- /dev/null +++ b/source/java/org/alfresco/repo/admin/patch/impl/GuestPersonPermissionPatch2.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.admin.patch.impl; + +import org.alfresco.i18n.I18NUtil; +import org.alfresco.repo.admin.patch.AbstractPatch; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.cmr.security.PersonService; + +/** + * Change Guest Person permission to make visible to all users as 'Consumer'. + * + * This allows users other than admin to select the Guest user for Invite to a Space. + */ +public class GuestPersonPermissionPatch2 extends AbstractPatch +{ + private static final String MSG_SUCCESS = "patch.guestPersonPermission2.result"; + + private PersonService personService; + + private PermissionService permissionService; + + private String guestId = "guest"; + + public GuestPersonPermissionPatch2() + { + super(); + } + + public void setGuestId(String guestId) + { + this.guestId = guestId; + } + + public void setPermissionService(PermissionService permissionService) + { + this.permissionService = permissionService; + } + + public void setPersonService(PersonService personService) + { + this.personService = personService; + } + + @Override + protected String applyInternal() throws Exception + { + if (personService.personExists(guestId)) + { + NodeRef personRef = personService.getPerson(guestId); + permissionService.setInheritParentPermissions(personRef, true); + } + + return I18NUtil.getMessage(MSG_SUCCESS); + } + +} diff --git a/source/java/org/alfresco/repo/admin/patch/impl/RSSTemplatesFolderPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/RSSTemplatesFolderPatch.java new file mode 100644 index 0000000000..e952e74bb5 --- /dev/null +++ b/source/java/org/alfresco/repo/admin/patch/impl/RSSTemplatesFolderPatch.java @@ -0,0 +1,306 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.admin.patch.impl; + +import java.io.IOException; +import java.io.Serializable; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import org.alfresco.i18n.I18NUtil; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.admin.patch.AbstractPatch; +import org.alfresco.repo.importer.ACPImportPackageHandler; +import org.alfresco.repo.importer.ImporterBootstrap; +import org.alfresco.service.cmr.admin.PatchException; +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.security.PermissionService; +import org.alfresco.service.cmr.view.ImporterService; +import org.alfresco.service.cmr.view.Location; +import org.alfresco.service.namespace.QName; +import org.springframework.context.MessageSource; +import org.springframework.core.io.ClassPathResource; + +/** + * Ensures that the RSS Templates folder is present. + *

+ * This uses the bootstrap importer to get the paths to look for. If not present, + * the required structures are created. + *

+ * This class should be replaced with a more generic ImporterPatch + * that can do conditional importing into given locations. + *

+ * JIRA: {@link http://www.alfresco.org/jira/browse/AR-342 AR-342} + * + * @author Kevin Roast + */ +public class RSSTemplatesFolderPatch extends AbstractPatch +{ + private static final String MSG_EXISTS = "patch.rssTemplatesFolder.result.exists"; + private static final String MSG_CREATED = "patch.rssTemplatesFolder.result.created"; + + public static final String PROPERTY_COMPANY_HOME_CHILDNAME = "spaces.company_home.childname"; + public static final String PROPERTY_DICTIONARY_CHILDNAME = "spaces.dictionary.childname"; + public static final String PROPERTY_RSS_FOLDER_CHILDNAME = "spaces.templates.rss.childname"; + private static final String PROPERTY_RSS_FOLDER_NAME = "spaces.templates.rss.name"; + private static final String PROPERTY_RSS_FOLDER_DESCRIPTION = "spaces.templates.rss.description"; + private static final String PROPERTY_ICON = "space-icon-default"; + + private ImporterBootstrap importerBootstrap; + private ImporterService importerService; + private MessageSource messageSource; + private PermissionService permissionService; + + protected NodeRef dictionaryNodeRef; + protected Properties configuration; + protected NodeRef rssFolderNodeRef; + + private String rssTemplatesACP; + + public void setPermissionService(PermissionService permissionService) + { + this.permissionService = permissionService; + } + + public void setImporterBootstrap(ImporterBootstrap importerBootstrap) + { + this.importerBootstrap = importerBootstrap; + } + + public void setImporterService(ImporterService importerService) + { + this.importerService = importerService; + } + + public void setMessageSource(MessageSource messageSource) + { + this.messageSource = messageSource; + } + + public void setRssTemplatesACP(String rssTemplatesACP) + { + this.rssTemplatesACP = rssTemplatesACP; + } + + /** + * Ensure that required common properties have been set + */ + protected void checkCommonProperties() throws Exception + { + checkPropertyNotNull(importerBootstrap, "importerBootstrap"); + checkPropertyNotNull(importerService, "importerService"); + checkPropertyNotNull(messageSource, "messageSource"); + if (namespaceService == null) + { + throw new PatchException("'namespaceService' property has not been set"); + } + else if (searchService == null) + { + throw new PatchException("'searchService' property has not been set"); + } + else if (nodeService == null) + { + throw new PatchException("'nodeService' property has not been set"); + } + checkPropertyNotNull(rssTemplatesACP, "rssTemplatesACP"); + } + + /** + * Extracts pertinent references and properties that are common to execution + * of this and derived patches. + */ + protected void setUp() throws Exception + { + // get the node store that we must work against + StoreRef storeRef = importerBootstrap.getStoreRef(); + if (storeRef == null) + { + throw new PatchException("Bootstrap store has not been set"); + } + NodeRef storeRootNodeRef = nodeService.getRootNode(storeRef); + + this.configuration = importerBootstrap.getConfiguration(); + // get the association names that form the path + String companyHomeChildName = configuration.getProperty(PROPERTY_COMPANY_HOME_CHILDNAME); + if (companyHomeChildName == null || companyHomeChildName.length() == 0) + { + throw new PatchException("Bootstrap property '" + PROPERTY_COMPANY_HOME_CHILDNAME + "' is not present"); + } + String dictionaryChildName = configuration.getProperty(PROPERTY_DICTIONARY_CHILDNAME); + if (dictionaryChildName == null || dictionaryChildName.length() == 0) + { + throw new PatchException("Bootstrap property '" + PROPERTY_DICTIONARY_CHILDNAME + "' is not present"); + } + String rssChildName = configuration.getProperty(PROPERTY_RSS_FOLDER_CHILDNAME); + if (rssChildName == null || rssChildName.length() == 0) + { + throw new PatchException("Bootstrap property '" + PROPERTY_RSS_FOLDER_CHILDNAME + "' is not present"); + } + + // build the search string to get the dictionary node + StringBuilder sb = new StringBuilder(256); + sb.append("/").append(companyHomeChildName) + .append("/").append(dictionaryChildName); + String xpath = sb.toString(); + + // get the dictionary node + List nodeRefs = searchService.selectNodes(storeRootNodeRef, xpath, null, namespaceService, false); + if (nodeRefs.size() == 0) + { + throw new PatchException("XPath didn't return any results: \n" + + " root: " + storeRootNodeRef + "\n" + + " xpath: " + xpath); + } + else if (nodeRefs.size() > 1) + { + throw new PatchException("XPath returned too many results: \n" + + " root: " + storeRootNodeRef + "\n" + + " xpath: " + xpath + "\n" + + " results: " + nodeRefs); + } + this.dictionaryNodeRef = nodeRefs.get(0); + + // Now we have the optional part - check for the existence of the RSS Templates folder + xpath = rssChildName; + nodeRefs = searchService.selectNodes(dictionaryNodeRef, xpath, null, namespaceService, false); + if (nodeRefs.size() > 1) + { + throw new PatchException("XPath returned too many results: \n" + + " dictionary node: " + dictionaryNodeRef + "\n" + + " xpath: " + xpath + "\n" + + " results: " + nodeRefs); + } + else if (nodeRefs.size() == 0) + { + // the node does not exist + this.rssFolderNodeRef = null; + } + else + { + // we have the RSS Templates folder noderef + this.rssFolderNodeRef = nodeRefs.get(0); + } + } + + @Override + protected String applyInternal() throws Exception + { + // properties must be set + checkCommonProperties(); + if (messageSource == null) + { + throw new PatchException("'messageSource' property has not been set"); + } + + // get useful values + setUp(); + + String msg = null; + if (rssFolderNodeRef == null) + { + // create it + createFolder(); + + // apply Guest permission to the folder + permissionService.setPermission( + rssFolderNodeRef, + PermissionService.GUEST_AUTHORITY, + PermissionService.CONSUMER, + true); + + // import the content + try + { + authenticationComponent.setCurrentUser(authenticationComponent.getSystemUserName()); + + importContent(); + } + finally + { + authenticationComponent.clearCurrentSecurityContext(); + } + + msg = I18NUtil.getMessage(MSG_CREATED, rssFolderNodeRef); + } + else + { + // it already exists + msg = I18NUtil.getMessage(MSG_EXISTS, rssFolderNodeRef); + } + // done + return msg; + } + + private void createFolder() + { + // get required properties + String rssChildName = configuration.getProperty(PROPERTY_RSS_FOLDER_CHILDNAME); + if (rssChildName == null) + { + throw new PatchException("Bootstrap property '" + PROPERTY_RSS_FOLDER_CHILDNAME + "' is not present"); + } + + String folderName = messageSource.getMessage( + PROPERTY_RSS_FOLDER_NAME, + null, + I18NUtil.getLocale()); + if (folderName == null || folderName.length() == 0) + { + throw new PatchException("Bootstrap property '" + PROPERTY_RSS_FOLDER_NAME + "' is not present"); + } + + String folderDescription = messageSource.getMessage( + PROPERTY_RSS_FOLDER_DESCRIPTION, + null, + I18NUtil.getLocale()); + if (folderDescription == null || folderDescription.length() == 0) + { + throw new PatchException("Bootstrap property '" + PROPERTY_RSS_FOLDER_DESCRIPTION + "' is not present"); + } + + Map properties = new HashMap(7); + properties.put(ContentModel.PROP_NAME, folderName); + properties.put(ContentModel.PROP_TITLE, folderName); + properties.put(ContentModel.PROP_DESCRIPTION, folderDescription); + properties.put(ContentModel.PROP_ICON, PROPERTY_ICON); + + // create the node + ChildAssociationRef childAssocRef = nodeService.createNode( + dictionaryNodeRef, + ContentModel.ASSOC_CONTAINS, + QName.resolveToQName(namespaceService, rssChildName), + ContentModel.TYPE_FOLDER, + properties); + this.rssFolderNodeRef = childAssocRef.getChildRef(); + + // finally add the required aspects + nodeService.addAspect(rssFolderNodeRef, ContentModel.ASPECT_UIFACETS, null); + } + + private void importContent() throws IOException + { + // import the content + ClassPathResource acpResource = new ClassPathResource(this.rssTemplatesACP); + ACPImportPackageHandler acpHandler = new ACPImportPackageHandler(acpResource.getFile(), null); + Location importLocation = new Location(this.rssFolderNodeRef); + importerService.importView(acpHandler, importLocation, null, null); + } +} diff --git a/source/java/org/alfresco/repo/admin/patch/impl/SchemaUpgradeScriptPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/SchemaUpgradeScriptPatch.java new file mode 100644 index 0000000000..69dfd1e092 --- /dev/null +++ b/source/java/org/alfresco/repo/admin/patch/impl/SchemaUpgradeScriptPatch.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.admin.patch.impl; + +import org.alfresco.repo.admin.patch.AbstractPatch; +import org.alfresco.service.cmr.admin.PatchException; + +/** + * This patch ensures that an upgrade script has been executed. Upgrade scripts + * should create an entry for the patch with the required ID and execution status + * so that the code in this class is never called. If called, an exception message + * is always generated. + * + * @author Derek Hulley + */ +public class SchemaUpgradeScriptPatch extends AbstractPatch +{ + private static final String MSG_NOT_EXECUTED = "patch.schemaUpgradeScript.err.not_executed"; + + private String scriptName; + + public SchemaUpgradeScriptPatch() + { + } + + /** + * Set the name of the upgrade script to execute. + * + * @param scriptName the script filename + */ + public void setScriptName(String scriptName) + { + this.scriptName = scriptName; + } + + protected void checkProperties() + { + super.checkProperties(); + checkPropertyNotNull(scriptName, "scriptName"); + } + + /** + * @see #MSG_NOT_EXECUTED + */ + @Override + protected String applyInternal() throws Exception + { + throw new PatchException(MSG_NOT_EXECUTED, scriptName); + } +} diff --git a/source/java/org/alfresco/repo/admin/patch/impl/ScriptsFolderPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/ScriptsFolderPatch.java index 6fea10daa8..73b409ac96 100644 --- a/source/java/org/alfresco/repo/admin/patch/impl/ScriptsFolderPatch.java +++ b/source/java/org/alfresco/repo/admin/patch/impl/ScriptsFolderPatch.java @@ -188,7 +188,7 @@ public class ScriptsFolderPatch extends AbstractPatch } else { - // we have the saved searches folder noderef + // we have the scripts folder noderef this.scriptsFolderNodeRef = nodeRefs.get(0); } } @@ -238,49 +238,47 @@ public class ScriptsFolderPatch extends AbstractPatch private void createFolder() { // get required properties - String savedSearchesChildName = configuration.getProperty(PROPERTY_SCRIPTS_FOLDER_CHILDNAME); - if (savedSearchesChildName == null) + String scriptsChildName = configuration.getProperty(PROPERTY_SCRIPTS_FOLDER_CHILDNAME); + if (scriptsChildName == null) { throw new PatchException("Bootstrap property '" + PROPERTY_SCRIPTS_FOLDER_CHILDNAME + "' is not present"); } - String savedSearchesName = messageSource.getMessage( + String folderName = messageSource.getMessage( PROPERTY_SCRIPTS_FOLDER_NAME, null, I18NUtil.getLocale()); - if (savedSearchesName == null || savedSearchesName.length() == 0) + if (folderName == null || folderName.length() == 0) { throw new PatchException("Bootstrap property '" + PROPERTY_SCRIPTS_FOLDER_NAME + "' is not present"); } - String savedSearchesDescription = messageSource.getMessage( + String folderDescription = messageSource.getMessage( PROPERTY_SCRIPTS_FOLDER_DESCRIPTION, null, I18NUtil.getLocale()); - if (savedSearchesDescription == null || savedSearchesDescription.length() == 0) + if (folderDescription == null || folderDescription.length() == 0) { throw new PatchException("Bootstrap property '" + PROPERTY_SCRIPTS_FOLDER_DESCRIPTION + "' is not present"); } Map properties = new HashMap(7); - properties.put(ContentModel.PROP_NAME, savedSearchesName); - properties.put(ContentModel.PROP_TITLE, savedSearchesName); - properties.put(ContentModel.PROP_DESCRIPTION, savedSearchesDescription); + properties.put(ContentModel.PROP_NAME, folderName); + properties.put(ContentModel.PROP_TITLE, folderName); + properties.put(ContentModel.PROP_DESCRIPTION, folderDescription); properties.put(ContentModel.PROP_ICON, PROPERTY_ICON); // create the node ChildAssociationRef childAssocRef = nodeService.createNode( dictionaryNodeRef, ContentModel.ASSOC_CONTAINS, - QName.resolveToQName(namespaceService, savedSearchesChildName), + QName.resolveToQName(namespaceService, scriptsChildName), ContentModel.TYPE_FOLDER, properties); scriptsFolderNodeRef = childAssocRef.getChildRef(); - // add the required aspects + // finally add the required aspects nodeService.addAspect(scriptsFolderNodeRef, ContentModel.ASPECT_UIFACETS, null); - - // done } private void importContent() throws IOException diff --git a/source/java/org/alfresco/repo/admin/patch/impl/SystemWorkflowFolderPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/SystemWorkflowFolderPatch.java new file mode 100644 index 0000000000..65d0c96ead --- /dev/null +++ b/source/java/org/alfresco/repo/admin/patch/impl/SystemWorkflowFolderPatch.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.admin.patch.impl; + +import org.alfresco.i18n.I18NUtil; +import org.alfresco.repo.admin.patch.AbstractPatch; +import org.alfresco.repo.workflow.WorkflowPackageImpl; +import org.alfresco.service.cmr.repository.NodeRef; + + +/** + * Ensures the system folder for Workflows is created. + * + * @author davidc + */ +public class SystemWorkflowFolderPatch extends AbstractPatch +{ + private static final String MSG_CREATED = "patch.systemWorkflowFolder.result.created"; + + private WorkflowPackageImpl workflowPackageImpl; + + public void setWorkflowPackageImpl(WorkflowPackageImpl workflowPackageImpl) + { + this.workflowPackageImpl = workflowPackageImpl; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.admin.patch.AbstractPatch#applyInternal() + */ + @Override + protected String applyInternal() throws Exception + { + NodeRef systemContainer = workflowPackageImpl.createSystemWorkflowContainer(); + return I18NUtil.getMessage(MSG_CREATED, systemContainer); + } + +} diff --git a/source/java/org/alfresco/repo/admin/patch/impl/UIFacetsAspectRemovalPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/UIFacetsAspectRemovalPatch.java new file mode 100644 index 0000000000..6aba06c231 --- /dev/null +++ b/source/java/org/alfresco/repo/admin/patch/impl/UIFacetsAspectRemovalPatch.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.admin.patch.impl; + +import java.io.IOException; +import java.util.List; +import java.util.Properties; + +import org.alfresco.i18n.I18NUtil; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.admin.patch.AbstractPatch; +import org.alfresco.repo.importer.ACPImportPackageHandler; +import org.alfresco.repo.importer.ImporterBootstrap; +import org.alfresco.service.cmr.admin.PatchException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.view.ImporterService; +import org.alfresco.service.cmr.view.Location; +import org.springframework.context.MessageSource; +import org.springframework.core.io.ClassPathResource; + +/** + * Removes the uifacets aspect incorrectly applied to the default set of Presentation + * Templates loaded during bootstrap. For new installs the bootstrap XML file has been modified + * to no longer apply the aspect. + *

+ * This uses the bootstrap importer to get the paths to look for. + * + * @author Kevin Roast + */ +public class UIFacetsAspectRemovalPatch extends AbstractPatch +{ + private static final String MSG_UPDATED = "patch.uifacetsAspectRemovalPatch.updated"; + + public static final String PROPERTY_COMPANY_HOME_CHILDNAME = "spaces.company_home.childname"; + public static final String PROPERTY_DICTIONARY_CHILDNAME = "spaces.dictionary.childname"; + public static final String PROPERTY_TEMPLATES_CHILDNAME = "spaces.templates.content.childname"; + + private ImporterBootstrap importerBootstrap; + private MessageSource messageSource; + + protected Properties configuration; + protected NodeRef templatesNodeRef; + + public void setImporterBootstrap(ImporterBootstrap importerBootstrap) + { + this.importerBootstrap = importerBootstrap; + } + + public void setMessageSource(MessageSource messageSource) + { + this.messageSource = messageSource; + } + + /** + * Ensure that required properties have been set + */ + protected void checkRequiredProperties() throws Exception + { + checkPropertyNotNull(importerBootstrap, "importerBootstrap"); + checkPropertyNotNull(messageSource, "messageSource"); + } + + /** + * Extracts pertinent references and properties that are common to execution + * of this and derived patches. + * + * @return the number of updated template files + */ + protected int removeAspectFromTemplates() throws Exception + { + // get the node store that we must work against + StoreRef storeRef = importerBootstrap.getStoreRef(); + if (storeRef == null) + { + throw new PatchException("Bootstrap store has not been set"); + } + NodeRef storeRootNodeRef = nodeService.getRootNode(storeRef); + + this.configuration = importerBootstrap.getConfiguration(); + + // get the association names that form the path + String companyHomeChildName = configuration.getProperty(PROPERTY_COMPANY_HOME_CHILDNAME); + if (companyHomeChildName == null || companyHomeChildName.length() == 0) + { + throw new PatchException("Bootstrap property '" + PROPERTY_COMPANY_HOME_CHILDNAME + "' is not present"); + } + String dictionaryChildName = configuration.getProperty(PROPERTY_DICTIONARY_CHILDNAME); + if (dictionaryChildName == null || dictionaryChildName.length() == 0) + { + throw new PatchException("Bootstrap property '" + PROPERTY_DICTIONARY_CHILDNAME + "' is not present"); + } + String templatesChildName = configuration.getProperty(PROPERTY_TEMPLATES_CHILDNAME); + if (templatesChildName == null || templatesChildName.length() == 0) + { + throw new PatchException("Bootstrap property '" + PROPERTY_TEMPLATES_CHILDNAME + "' is not present"); + } + + // build the search string to get the email templates node + StringBuilder sb = new StringBuilder(128); + sb.append("/").append(companyHomeChildName) + .append("/").append(dictionaryChildName) + .append("/").append(templatesChildName) + .append("//*[subtypeOf('cm:content')]"); + String xpath = sb.toString(); + + // get the template content nodes + int updated = 0; + List nodeRefs = searchService.selectNodes(storeRootNodeRef, xpath, null, namespaceService, false); + for (NodeRef ref : nodeRefs) + { + // if the content has the uifacets aspect, then remove it and meaningless icon reference + if (nodeService.hasAspect(ref, ContentModel.ASPECT_UIFACETS)) + { + nodeService.removeAspect(ref, ContentModel.ASPECT_UIFACETS); + nodeService.setProperty(ref, ContentModel.PROP_ICON, null); + updated++; + } + } + return updated; + } + + @Override + protected String applyInternal() throws Exception + { + // common properties must be set before we can continue + checkRequiredProperties(); + + int updated = removeAspectFromTemplates(); + + // output a message to describe the result + return I18NUtil.getMessage(MSG_UPDATED, updated); + } +} diff --git a/source/java/org/alfresco/repo/admin/patch/impl/UniqueChildNamePatch.java b/source/java/org/alfresco/repo/admin/patch/impl/UniqueChildNamePatch.java new file mode 100644 index 0000000000..d2525361cd --- /dev/null +++ b/source/java/org/alfresco/repo/admin/patch/impl/UniqueChildNamePatch.java @@ -0,0 +1,268 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.admin.patch.impl; + +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.util.Date; +import java.util.List; + +import org.alfresco.i18n.I18NUtil; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.admin.patch.AbstractPatch; +import org.alfresco.repo.domain.ChildAssoc; +import org.alfresco.repo.domain.Node; +import org.alfresco.repo.node.db.NodeDaoService; +import org.alfresco.service.cmr.dictionary.AssociationDefinition; +import org.alfresco.service.cmr.dictionary.ChildAssociationDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.repository.DuplicateChildNodeNameException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.Path; +import org.alfresco.service.namespace.QName; +import org.hibernate.Query; +import org.hibernate.Session; +import org.hibernate.SessionFactory; +import org.springframework.orm.hibernate3.HibernateCallback; +import org.springframework.orm.hibernate3.support.HibernateDaoSupport; + +/** + * Checks that all child node names are unique for the associations that require it. + * + * @author Derek Hulley + */ +public class UniqueChildNamePatch extends AbstractPatch +{ + private static final String MSG_SUCCESS = "patch.uniqueChildName.result"; + private static final String MSG_COPY_OF = "patch.uniqueChildName.copyOf"; + /** the number of associations to process at a time */ + private static final int MAX_RESULTS = 1000; + + private SessionFactory sessionFactory; + private DictionaryService dictionaryService; + private NodeDaoService nodeDaoService; + + public UniqueChildNamePatch() + { + } + + public void setSessionFactory(SessionFactory sessionFactory) + { + this.sessionFactory = sessionFactory; + } + + /** + * @param dictionaryService The service used to sort out the associations + * that require duplicate checks + */ + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + /** + * @param nodeDaoService The service that generates the CRC values + */ + public void setNodeDaoService(NodeDaoService nodeDaoService) + { + this.nodeDaoService = nodeDaoService; + } + + @Override + protected void checkProperties() + { + super.checkProperties(); + checkPropertyNotNull(sessionFactory, "sessionFactory"); + checkPropertyNotNull(dictionaryService, "dictionaryService"); + checkPropertyNotNull(nodeDaoService, "nodeDaoService"); + } + + @Override + protected String applyInternal() throws Exception + { + // initialise the helper + HibernateHelper helper = new HibernateHelper(); + helper.setSessionFactory(sessionFactory); + + String msg = helper.assignCrc(); + + // done + return msg; + } + + private class HibernateHelper extends HibernateDaoSupport + { + private File logFile; + private FileChannel channel; + + private HibernateHelper() throws IOException + { + logFile = new File("./UniqueChildNamePatch.log"); + // open the file for appending + RandomAccessFile outputFile = new RandomAccessFile(logFile, "rw"); + channel = outputFile.getChannel(); + // move to the end of the file + channel.position(channel.size()); + // add a newline and it's ready + writeLine("").writeLine(""); + writeLine("UniqueChildNamePatch executing on " + new Date()); + } + + private HibernateHelper write(Object obj) throws IOException + { + channel.write(ByteBuffer.wrap(obj.toString().getBytes())); + return this; + } + private HibernateHelper writeLine(Object obj) throws IOException + { + write(obj); + write("\n"); + return this; + } + + public String assignCrc() throws Exception + { + // get the association types to check + @SuppressWarnings("unused") + List assocTypeQNames = getUsedAssocQNames(); + + int fixed = 0; + int processed = 0; + // check loop through all associations, looking for duplicates + for (QName assocTypeQName : assocTypeQNames) + { + AssociationDefinition assocDef = dictionaryService.getAssociation(assocTypeQName); + if (!(assocDef instanceof ChildAssociationDefinition)) + { + String msg = "WARNING: Non-child association used to link a child node: " + assocTypeQName; + writeLine(msg); + logger.warn(msg); + continue; + } + ChildAssociationDefinition childAssocDef = (ChildAssociationDefinition) assocDef; + if (childAssocDef.getDuplicateChildNamesAllowed()) + { + continue; + } + write("Checking for name duplicates on association type ").writeLine(assocTypeQName); + + // get all child associations until there are no more results + long lastAssocId = Long.MIN_VALUE; + int lastResultCount = 1; + while(lastResultCount > 0) + { + writeLine(String.format("...Processed %7d associations with %3d duplicates found...", processed, fixed)); + + List results = getAssociations(assocTypeQName, lastAssocId) ; + lastResultCount = results.size(); + for (Object[] objects : results) + { + ChildAssoc childAssoc = (ChildAssoc) objects[0]; + Node childNode = (Node) objects[1]; + NodeRef childNodeRef = childNode.getNodeRef(); + + // get the current name + String childName = (String) nodeService.getProperty(childNodeRef, ContentModel.PROP_NAME); + + lastAssocId = childAssoc.getId(); + String usedChildName = childName; + processed++; + boolean duplicate = false; + while(true) + { + try + { + // push the name back to the node + nodeService.setProperty(childNodeRef, ContentModel.PROP_NAME, usedChildName); + break; // no issues - no duplicate + } + catch (DuplicateChildNodeNameException e) + { + // there was a duplicate, so adjust the name and change the node property + duplicate = true; + // assign a new name + usedChildName = childName + I18NUtil.getMessage(MSG_COPY_OF, processed); + // try again + } + } + // if duplicated, report it + if (duplicate) + { + fixed++; + // get the node path + NodeRef parentNodeRef = childAssoc.getParent().getNodeRef(); + Path path = nodeService.getPath(parentNodeRef); + writeLine(" Changed duplicated child name:"); + writeLine(" Parent: " + parentNodeRef); + writeLine(" Parent path: " + path); + writeLine(" Duplicate name: " + childName); + writeLine(" Replaced with: " + usedChildName); + } + } + } + } + + + // build the result message + String msg = I18NUtil.getMessage(MSG_SUCCESS, processed, fixed, logFile); + return msg; + } + + @SuppressWarnings("unchecked") + private List getUsedAssocQNames() + { + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + Query query = session + .createQuery( + "select distinct assoc.typeQName " + + "from org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc"); + return query.list(); + } + }; + List results = (List) getHibernateTemplate().execute(callback); + return results; + } + + /** + * @return Returns a list of ChildAssoc and String instances + */ + @SuppressWarnings("unchecked") + private List getAssociations(final QName assocTypeQName, final long lastAssocId) + { + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + Query query = session + .getNamedQuery("node.patch.GetAssocsAndChildNames") + .setLong("lastAssocId", lastAssocId) + .setParameter("assocTypeQName", assocTypeQName) + .setMaxResults(MAX_RESULTS); + return query.list(); + } + }; + List results = (List) getHibernateTemplate().execute(callback); + return results; + } + } +} diff --git a/source/java/org/alfresco/repo/avm/AVMNodeService.java b/source/java/org/alfresco/repo/avm/AVMNodeService.java index 8c49ca808d..eeb0327900 100644 --- a/source/java/org/alfresco/repo/avm/AVMNodeService.java +++ b/source/java/org/alfresco/repo/avm/AVMNodeService.java @@ -1134,7 +1134,6 @@ public class AVMNodeService extends AbstractNodeServiceImpl implements NodeServi * @see QName * @see org.alfresco.service.namespace.RegexQNamePattern#MATCH_ALL */ - @Auditable(key = Auditable.Key.ARG_0 ,parameters = {"nodeRef", "typeQNamePattern", "qnamePattern"}) public List getChildAssocs( NodeRef nodeRef, QNamePattern typeQNamePattern, @@ -1158,6 +1157,32 @@ public class AVMNodeService extends AbstractNodeServiceImpl implements NodeServi return result; } + /** + * Get a child NodeRef by name. + * @param nodeRef The parent node. + * @param assocTypeQName The type of the Child Association. + * @param childName The name of the child to get. + */ + public NodeRef getChildByName(NodeRef nodeRef, QName assocTypeQName, String childName) + { + if (!assocTypeQName.equals(ContentModel.ASSOC_CONTAINS)) + { + return null; + } + Object [] avmVersionPath = AVMNodeConverter.ToAVMVersionPath(nodeRef); + try + { + AVMNodeDescriptor child = fAVMService.lookup((Integer)avmVersionPath[0], + (String)avmVersionPath[1]); + return AVMNodeConverter.ToNodeRef((Integer)avmVersionPath[0], + child.getPath()); + } + catch (AVMException e) + { + return null; + } + } + /** * Fetches the primary parent-child relationship. *

diff --git a/source/java/org/alfresco/repo/avm/AVMServiceTestBase.java b/source/java/org/alfresco/repo/avm/AVMServiceTestBase.java index ce1e0132c0..6a717b27f6 100644 --- a/source/java/org/alfresco/repo/avm/AVMServiceTestBase.java +++ b/source/java/org/alfresco/repo/avm/AVMServiceTestBase.java @@ -38,17 +38,17 @@ public class AVMServiceTestBase extends TestCase /** * The AVMService we are testing. */ - protected AVMService fService; + protected static AVMService fService; /** * The reaper thread. */ - protected OrphanReaper fReaper; + protected static OrphanReaper fReaper; /** * The application context. */ - protected FileSystemXmlApplicationContext fContext; + protected static FileSystemXmlApplicationContext fContext; /** * The start time of actual work for a test. @@ -63,9 +63,12 @@ public class AVMServiceTestBase extends TestCase @Override protected void setUp() throws Exception { - fContext = new FileSystemXmlApplicationContext("config/alfresco/avm-test-context.xml"); - fService = (AVMService)fContext.getBean("AVMService"); - fReaper = (OrphanReaper)fContext.getBean("orphanReaper"); + if (fContext == null) + { + fContext = new FileSystemXmlApplicationContext("config/alfresco/avm-test-context.xml"); + fService = (AVMService)fContext.getBean("AVMService"); + fReaper = (OrphanReaper)fContext.getBean("orphanReaper"); + } fStartTime = System.currentTimeMillis(); } @@ -83,10 +86,11 @@ public class AVMServiceTestBase extends TestCase { fService.purgeAVMStore(desc.getName()); } - fContext.close(); - File alfData = new File("alf_data"); - File target = new File("alf_data" + now); - alfData.renameTo(target); + fService.createAVMStore("main"); + // fContext.close(); + // File alfData = new File("alf_data"); + // File target = new File("alf_data" + now); + // alfData.renameTo(target); } /** diff --git a/source/java/org/alfresco/repo/avm/hibernate/AVM.hbm.xml b/source/java/org/alfresco/repo/avm/hibernate/AVM.hbm.xml index 3367766135..344cad2f6b 100644 --- a/source/java/org/alfresco/repo/avm/hibernate/AVM.hbm.xml +++ b/source/java/org/alfresco/repo/avm/hibernate/AVM.hbm.xml @@ -78,7 +78,7 @@ - + diff --git a/source/java/org/alfresco/repo/coci/CheckOutCheckInServiceImpl.java b/source/java/org/alfresco/repo/coci/CheckOutCheckInServiceImpl.java index ccfb32b96c..ae430ef149 100644 --- a/source/java/org/alfresco/repo/coci/CheckOutCheckInServiceImpl.java +++ b/source/java/org/alfresco/repo/coci/CheckOutCheckInServiceImpl.java @@ -46,7 +46,7 @@ import org.alfresco.service.namespace.QName; * * @author Roy Wetherall */ -public class CheckOutCheckInServiceImpl implements CheckOutCheckInService +public class CheckOutCheckInServiceImpl implements CheckOutCheckInService { /** * I18N labels @@ -58,30 +58,30 @@ public class CheckOutCheckInServiceImpl implements CheckOutCheckInService private static final String MSG_ERR_NOT_AUTHENTICATED = "coci_service.err_not_authenticated"; private static final String MSG_ERR_WORKINGCOPY_HAS_NO_MIMETYPE = "coci_service.err_workingcopy_has_no_mimetype"; - /** - * Extension character, used to recalculate the working copy names - */ - private static final String EXTENSION_CHARACTER = "."; - - /** - * The node service - */ - private NodeService nodeService; - - /** - * The version service - */ - private VersionService versionService; - - /** - * The lock service - */ - private LockService lockService; - - /** - * The copy service - */ - private CopyService copyService; + /** + * Extension character, used to recalculate the working copy names + */ + private static final String EXTENSION_CHARACTER = "."; + + /** + * The node service + */ + private NodeService nodeService; + + /** + * The version service + */ + private VersionService versionService; + + /** + * The lock service + */ + private LockService lockService; + + /** + * The copy service + */ + private CopyService copyService; /** * The search service @@ -97,47 +97,47 @@ public class CheckOutCheckInServiceImpl implements CheckOutCheckInService * The versionable aspect behaviour implementation */ private VersionableAspect versionableAspect; - - /** - * Set the node service - * - * @param nodeService the node service - */ - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - /** - * Set the version service - * - * @param versionService the version service - */ - public void setVersionService(VersionService versionService) - { - this.versionService = versionService; - } - - /** - * Sets the lock service - * - * @param lockService the lock service - */ - public void setLockService(LockService lockService) - { - this.lockService = lockService; - } - - /** + + /** + * Set the node service + * + * @param nodeService the node service + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * Set the version service + * + * @param versionService the version service + */ + public void setVersionService(VersionService versionService) + { + this.versionService = versionService; + } + + /** + * Sets the lock service + * + * @param lockService the lock service + */ + public void setLockService(LockService lockService) + { + this.lockService = lockService; + } + + /** * Sets the copy service * * @param copyService the copy service - */ - public void setCopyService( - CopyService copyService) - { - this.copyService = copyService; - } + */ + public void setCopyService( + CopyService copyService) + { + this.copyService = copyService; + } /** * Sets the authentication service @@ -173,57 +173,57 @@ public class CheckOutCheckInServiceImpl implements CheckOutCheckInService /** * Get the working copy label. * - * @return the working copy label + * @return the working copy label */ public String getWorkingCopyLabel() { - return I18NUtil.getMessage(MSG_WORKING_COPY_LABEL); - } - - /** - * @see org.alfresco.service.cmr.coci.CheckOutCheckInService#checkout(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName, org.alfresco.service.namespace.QName) - */ - public NodeRef checkout( - NodeRef nodeRef, - NodeRef destinationParentNodeRef, - QName destinationAssocTypeQName, - QName destinationAssocQName) - { - // Make sure we are no checking out a working copy node - if (this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_WORKING_COPY) == true) - { - throw new CheckOutCheckInServiceException(MSG_ERR_ALREADY_WORKING_COPY); - } - - // Apply the lock aspect if required - if (this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_LOCKABLE) == false) - { - this.nodeService.addAspect(nodeRef, ContentModel.ASPECT_LOCKABLE, null); - } - - // Rename the working copy + return I18NUtil.getMessage(MSG_WORKING_COPY_LABEL); + } + + /** + * @see org.alfresco.service.cmr.coci.CheckOutCheckInService#checkout(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName, org.alfresco.service.namespace.QName) + */ + public NodeRef checkout( + NodeRef nodeRef, + NodeRef destinationParentNodeRef, + QName destinationAssocTypeQName, + QName destinationAssocQName) + { + // Make sure we are no checking out a working copy node + if (this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_WORKING_COPY) == true) + { + throw new CheckOutCheckInServiceException(MSG_ERR_ALREADY_WORKING_COPY); + } + + // Apply the lock aspect if required + if (this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_LOCKABLE) == false) + { + this.nodeService.addAspect(nodeRef, ContentModel.ASPECT_LOCKABLE, null); + } + + // Rename the working copy String copyName = (String)this.nodeService.getProperty(nodeRef, ContentModel.PROP_NAME); - if (this.getWorkingCopyLabel() != null && this.getWorkingCopyLabel().length() != 0) - { - if (copyName != null && copyName.length() != 0) - { - int index = copyName.lastIndexOf(EXTENSION_CHARACTER); - if (index > 0) - { - // Insert the working copy label before the file extension + if (this.getWorkingCopyLabel() != null && this.getWorkingCopyLabel().length() != 0) + { + if (copyName != null && copyName.length() != 0) + { + int index = copyName.lastIndexOf(EXTENSION_CHARACTER); + if (index > 0) + { + // Insert the working copy label before the file extension copyName = copyName.substring(0, index) + " " + getWorkingCopyLabel() + copyName.substring(index); - } - else - { - // Simply append the working copy label onto the end of the existing name + } + else + { + // Simply append the working copy label onto the end of the existing name copyName = copyName + " " + getWorkingCopyLabel(); - } - } + } + } else { copyName = getWorkingCopyLabel(); } - } + } // Make the working copy destinationAssocQName = QName.createQName(destinationAssocQName.getNamespaceURI(), QName.createValidLocalName(copyName)); @@ -236,20 +236,24 @@ public class CheckOutCheckInServiceImpl implements CheckOutCheckInService // Update the working copy name this.nodeService.setProperty(workingCopy, ContentModel.PROP_NAME, copyName); - // Get the user - String userName = getUserName(); - - // Apply the working copy aspect to the working copy - Map workingCopyProperties = new HashMap(1); - workingCopyProperties.put(ContentModel.PROP_WORKING_COPY_OWNER, userName); - this.nodeService.addAspect(workingCopy, ContentModel.ASPECT_WORKING_COPY, workingCopyProperties); - - // Lock the original node - this.lockService.lock(nodeRef, LockType.READ_ONLY_LOCK); - - // Return the working copy - return workingCopy; - } + // Get the user + String userName = getUserName(); + + // Apply the working copy aspect to the working copy + Map workingCopyProperties = new HashMap(1); + workingCopyProperties.put(ContentModel.PROP_WORKING_COPY_OWNER, userName); + this.nodeService.addAspect(workingCopy, ContentModel.ASPECT_WORKING_COPY, workingCopyProperties); + + // Apply the sys:temporary aspect to tag the working copy as a temporary node + // so it doesn't get archived when checked in + this.nodeService.addAspect(workingCopy, ContentModel.ASPECT_TEMPORARY, null); + + // Lock the origional node + this.lockService.lock(nodeRef, LockType.READ_ONLY_LOCK); + + // Return the working copy + return workingCopy; + } /** * Gets the authenticated users node reference @@ -269,64 +273,64 @@ public class CheckOutCheckInServiceImpl implements CheckOutCheckInService } } - /** - * @see org.alfresco.service.cmr.coci.CheckOutCheckInService#checkout(org.alfresco.service.cmr.repository.NodeRef) - */ - public NodeRef checkout(NodeRef nodeRef) - { - // Find the primary parent in order to determine where to put the copy - ChildAssociationRef childAssocRef = this.nodeService.getPrimaryParent(nodeRef); - - // Checkout the working copy to the same destination - return checkout(nodeRef, childAssocRef.getParentRef(), childAssocRef.getTypeQName(), childAssocRef.getQName()); - } + /** + * @see org.alfresco.service.cmr.coci.CheckOutCheckInService#checkout(org.alfresco.service.cmr.repository.NodeRef) + */ + public NodeRef checkout(NodeRef nodeRef) + { + // Find the primary parent in order to determine where to put the copy + ChildAssociationRef childAssocRef = this.nodeService.getPrimaryParent(nodeRef); + + // Checkout the working copy to the same destination + return checkout(nodeRef, childAssocRef.getParentRef(), childAssocRef.getTypeQName(), childAssocRef.getQName()); + } - /** - * @see org.alfresco.repo.version.operations.VersionOperationsService#checkin(org.alfresco.repo.ref.NodeRef, Map, java.lang.String, boolean) - */ - public NodeRef checkin( - NodeRef workingCopyNodeRef, - Map versionProperties, - String contentUrl, - boolean keepCheckedOut) - { - NodeRef nodeRef = null; - - // Check that we have been handed a working copy - if (this.nodeService.hasAspect(workingCopyNodeRef, ContentModel.ASPECT_WORKING_COPY) == false) - { - // Error since we have not been passed a working copy - throw new AspectMissingException(ContentModel.ASPECT_WORKING_COPY, workingCopyNodeRef); - } - - // Check that the working node still has the copy aspect applied - if (this.nodeService.hasAspect(workingCopyNodeRef, ContentModel.ASPECT_COPIEDFROM) == true) - { + /** + * @see org.alfresco.repo.version.operations.VersionOperationsService#checkin(org.alfresco.repo.ref.NodeRef, Map, java.lang.String, boolean) + */ + public NodeRef checkin( + NodeRef workingCopyNodeRef, + Map versionProperties, + String contentUrl, + boolean keepCheckedOut) + { + NodeRef nodeRef = null; + + // Check that we have been handed a working copy + if (this.nodeService.hasAspect(workingCopyNodeRef, ContentModel.ASPECT_WORKING_COPY) == false) + { + // Error since we have not been passed a working copy + throw new AspectMissingException(ContentModel.ASPECT_WORKING_COPY, workingCopyNodeRef); + } + + // Check that the working node still has the copy aspect applied + if (this.nodeService.hasAspect(workingCopyNodeRef, ContentModel.ASPECT_COPIEDFROM) == true) + { // Disable versionable behaviours since we don't want the auto version policy behaviour to execute when we check-in - this.versionableAspect.disableAutoVersion(); - try - { + //this.versionableAspect.disableAutoVersion(); + //try + //{ Map workingCopyProperties = nodeService.getProperties(workingCopyNodeRef); - // Try and get the original node reference - nodeRef = (NodeRef) workingCopyProperties.get(ContentModel.PROP_COPY_REFERENCE); - if(nodeRef == null) - { - // Error since the original node can not be found - throw new CheckOutCheckInServiceException(MSG_ERR_BAD_COPY); - } - - try - { - // Release the lock - this.lockService.unlock(nodeRef); - } - catch (UnableToReleaseLockException exception) - { - throw new CheckOutCheckInServiceException(MSG_ERR_NOT_OWNER, exception); - } - - if (contentUrl != null) - { + // Try and get the original node reference + nodeRef = (NodeRef) workingCopyProperties.get(ContentModel.PROP_COPY_REFERENCE); + if(nodeRef == null) + { + // Error since the original node can not be found + throw new CheckOutCheckInServiceException(MSG_ERR_BAD_COPY); + } + + try + { + // Release the lock + this.lockService.unlock(nodeRef); + } + catch (UnableToReleaseLockException exception) + { + throw new CheckOutCheckInServiceException(MSG_ERR_NOT_OWNER, exception); + } + + if (contentUrl != null) + { ContentData contentData = (ContentData) workingCopyProperties.get(ContentModel.PROP_CONTENT); if (contentData == null) { @@ -340,15 +344,15 @@ public class CheckOutCheckInServiceImpl implements CheckOutCheckInService contentData.getSize(), contentData.getEncoding()); } - // Set the content url value onto the working copy - this.nodeService.setProperty( - workingCopyNodeRef, - ContentModel.PROP_CONTENT, - contentData); - } + // Set the content url value onto the working copy + this.nodeService.setProperty( + workingCopyNodeRef, + ContentModel.PROP_CONTENT, + contentData); + } - // Copy the contents of the working copy onto the original - this.copyService.copy(workingCopyNodeRef, nodeRef); + // Copy the contents of the working copy onto the original + this.copyService.copy(workingCopyNodeRef, nodeRef); if (versionProperties != null && this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE) == true) { @@ -356,94 +360,94 @@ public class CheckOutCheckInServiceImpl implements CheckOutCheckInService this.versionService.createVersion(nodeRef, versionProperties); } - if (keepCheckedOut == false) - { - // Delete the working copy + if (keepCheckedOut == false) + { + // Delete the working copy this.nodeService.removeAspect(workingCopyNodeRef, ContentModel.ASPECT_WORKING_COPY); - this.nodeService.deleteNode(workingCopyNodeRef); - } - else - { - // Re-lock the original node - this.lockService.lock(nodeRef, LockType.READ_ONLY_LOCK); - } - } - finally + this.nodeService.deleteNode(workingCopyNodeRef); + } + else + { + // Re-lock the original node + this.lockService.lock(nodeRef, LockType.READ_ONLY_LOCK); + } + //} + //finally + //{ + // this.versionableAspect.enableAutoVersion(); + //} + + } + else + { + // Error since the copy aspect is missing + throw new AspectMissingException(ContentModel.ASPECT_COPIEDFROM, workingCopyNodeRef); + } + + return nodeRef; + } + + /** + * @see org.alfresco.service.cmr.coci.CheckOutCheckInService#checkin(org.alfresco.service.cmr.repository.NodeRef, Map, java.lang.String) + */ + public NodeRef checkin( + NodeRef workingCopyNodeRef, + Map versionProperties, + String contentUrl) + { + return checkin(workingCopyNodeRef, versionProperties, contentUrl, false); + } + + /** + * @see org.alfresco.service.cmr.coci.CheckOutCheckInService#checkin(org.alfresco.service.cmr.repository.NodeRef, Map) + */ + public NodeRef checkin( + NodeRef workingCopyNodeRef, + Map versionProperties) + { + return checkin(workingCopyNodeRef, versionProperties, null, false); + } + + /** + * @see org.alfresco.service.cmr.coci.CheckOutCheckInService#cancelCheckout(org.alfresco.service.cmr.repository.NodeRef) + */ + public NodeRef cancelCheckout(NodeRef workingCopyNodeRef) + { + NodeRef nodeRef = null; + + // Check that we have been handed a working copy + if (this.nodeService.hasAspect(workingCopyNodeRef, ContentModel.ASPECT_WORKING_COPY) == false) + { + // Error since we have not been passed a working copy + throw new AspectMissingException(ContentModel.ASPECT_WORKING_COPY, workingCopyNodeRef); + } + + // Ensure that the node has the copy aspect + if (this.nodeService.hasAspect(workingCopyNodeRef, ContentModel.ASPECT_COPIEDFROM) == true) + { + // Get the original node + nodeRef = (NodeRef)this.nodeService.getProperty(workingCopyNodeRef, ContentModel.PROP_COPY_REFERENCE); + if (nodeRef == null) { - this.versionableAspect.enableAutoVersion(); - } - - } - else - { - // Error since the copy aspect is missing - throw new AspectMissingException(ContentModel.ASPECT_COPIEDFROM, workingCopyNodeRef); - } - - return nodeRef; - } - - /** - * @see org.alfresco.service.cmr.coci.CheckOutCheckInService#checkin(org.alfresco.service.cmr.repository.NodeRef, Map, java.lang.String) - */ - public NodeRef checkin( - NodeRef workingCopyNodeRef, - Map versionProperties, - String contentUrl) - { - return checkin(workingCopyNodeRef, versionProperties, contentUrl, false); - } - - /** - * @see org.alfresco.service.cmr.coci.CheckOutCheckInService#checkin(org.alfresco.service.cmr.repository.NodeRef, Map) - */ - public NodeRef checkin( - NodeRef workingCopyNodeRef, - Map versionProperties) - { - return checkin(workingCopyNodeRef, versionProperties, null, false); - } - - /** - * @see org.alfresco.service.cmr.coci.CheckOutCheckInService#cancelCheckout(org.alfresco.service.cmr.repository.NodeRef) - */ - public NodeRef cancelCheckout(NodeRef workingCopyNodeRef) - { - NodeRef nodeRef = null; - - // Check that we have been handed a working copy - if (this.nodeService.hasAspect(workingCopyNodeRef, ContentModel.ASPECT_WORKING_COPY) == false) - { - // Error since we have not been passed a working copy - throw new AspectMissingException(ContentModel.ASPECT_WORKING_COPY, workingCopyNodeRef); - } - - // Ensure that the node has the copy aspect - if (this.nodeService.hasAspect(workingCopyNodeRef, ContentModel.ASPECT_COPIEDFROM) == true) - { - // Get the original node - nodeRef = (NodeRef)this.nodeService.getProperty(workingCopyNodeRef, ContentModel.PROP_COPY_REFERENCE); - if (nodeRef == null) - { - // Error since the original node can not be found - throw new CheckOutCheckInServiceException(MSG_ERR_BAD_COPY); - } - - // Release the lock on the original node - this.lockService.unlock(nodeRef); - - // Delete the working copy + // Error since the original node can not be found + throw new CheckOutCheckInServiceException(MSG_ERR_BAD_COPY); + } + + // Release the lock on the original node + this.lockService.unlock(nodeRef); + + // Delete the working copy this.nodeService.removeAspect(workingCopyNodeRef, ContentModel.ASPECT_WORKING_COPY); - this.nodeService.deleteNode(workingCopyNodeRef); - } - else - { - // Error since the copy aspect is missing - throw new AspectMissingException(ContentModel.ASPECT_COPIEDFROM, workingCopyNodeRef); - } - - return nodeRef; - } + this.nodeService.deleteNode(workingCopyNodeRef); + } + else + { + // Error since the copy aspect is missing + throw new AspectMissingException(ContentModel.ASPECT_COPIEDFROM, workingCopyNodeRef); + } + + return nodeRef; + } /** * @see org.alfresco.service.cmr.coci.CheckOutCheckInService#getWorkingCopy(org.alfresco.service.cmr.repository.NodeRef) @@ -454,6 +458,7 @@ public class CheckOutCheckInServiceImpl implements CheckOutCheckInService // Do a search to find the working copy document ResultSet resultSet = null; + try { resultSet = this.searchService.query( diff --git a/source/java/org/alfresco/repo/coci/CheckOutCheckInServiceImplTest.java b/source/java/org/alfresco/repo/coci/CheckOutCheckInServiceImplTest.java index e9b02fada2..c8f4edc937 100644 --- a/source/java/org/alfresco/repo/coci/CheckOutCheckInServiceImplTest.java +++ b/source/java/org/alfresco/repo/coci/CheckOutCheckInServiceImplTest.java @@ -146,8 +146,8 @@ public class CheckOutCheckInServiceImplTest extends BaseSpringTest TestWithUserUtils.authenticateUser(this.userName, PWD, this.rootNodeRef, this.authenticationService); this.userNodeRef = TestWithUserUtils.getCurrentUser(this.authenticationService); - permissionService.setPermission(this.rootNodeRef, this.userName.toLowerCase(), PermissionService.ALL_PERMISSIONS, true); - permissionService.setPermission(this.nodeRef, this.userName.toLowerCase(), PermissionService.ALL_PERMISSIONS, true); + permissionService.setPermission(this.rootNodeRef, this.userName, PermissionService.ALL_PERMISSIONS, true); + permissionService.setPermission(this.nodeRef, this.userName, PermissionService.ALL_PERMISSIONS, true); } /** diff --git a/source/java/org/alfresco/repo/content/AbstractContentAccessor.java b/source/java/org/alfresco/repo/content/AbstractContentAccessor.java index efee5c1b4b..2845aa837f 100644 --- a/source/java/org/alfresco/repo/content/AbstractContentAccessor.java +++ b/source/java/org/alfresco/repo/content/AbstractContentAccessor.java @@ -150,14 +150,6 @@ public abstract class AbstractContentAccessor implements ContentAccessor } } - /** - * Derived classes must implement this to help determine if the underlying - * IO Channel is still open. - * - * @return Returns true if the underlying IO Channel is open - */ - protected abstract boolean isChannelOpen(); - public String getContentUrl() { return contentUrl; diff --git a/source/java/org/alfresco/repo/content/AbstractContentReader.java b/source/java/org/alfresco/repo/content/AbstractContentReader.java index a908b500d6..d3483c8b71 100644 --- a/source/java/org/alfresco/repo/content/AbstractContentReader.java +++ b/source/java/org/alfresco/repo/content/AbstractContentReader.java @@ -141,8 +141,7 @@ public abstract class AbstractContentReader extends AbstractContentAccessor impl } } - /** helper implementation for base class */ - protected boolean isChannelOpen() + public synchronized boolean isChannelOpen() { if (channel != null) { diff --git a/source/java/org/alfresco/repo/content/AbstractContentWriter.java b/source/java/org/alfresco/repo/content/AbstractContentWriter.java index 234da69207..30c3e5e35a 100644 --- a/source/java/org/alfresco/repo/content/AbstractContentWriter.java +++ b/source/java/org/alfresco/repo/content/AbstractContentWriter.java @@ -156,8 +156,7 @@ public abstract class AbstractContentWriter extends AbstractContentAccessor impl } } - /** helper implementation for base class */ - protected boolean isChannelOpen() + public synchronized boolean isChannelOpen() { if (channel != null) { diff --git a/source/java/org/alfresco/repo/content/MimetypeMap.java b/source/java/org/alfresco/repo/content/MimetypeMap.java index 08b9d49f24..272c1cb89d 100644 --- a/source/java/org/alfresco/repo/content/MimetypeMap.java +++ b/source/java/org/alfresco/repo/content/MimetypeMap.java @@ -57,6 +57,7 @@ public class MimetypeMap implements MimetypeService public static final String MIMETYPE_IMAGE_JPEG = "image/jpeg"; public static final String MIMETYPE_IMAGE_RGB = "image/x-rgb"; public static final String MIMETYPE_JAVASCRIPT = "application/x-javascript"; + public static final String MIMETYPE_ZIP = "application/zip"; // Open Document public static final String MIMETYPE_OPENDOCUMENT_TEXT = "application/vnd.oasis.opendocument.text"; public static final String MIMETYPE_OPENDOCUMENT_TEXT_TEMPLATE = "application/vnd.oasis.opendocument.text-template"; diff --git a/source/java/org/alfresco/repo/content/RoutingContentService.java b/source/java/org/alfresco/repo/content/RoutingContentService.java index dbec8e1099..506f9476a7 100644 --- a/source/java/org/alfresco/repo/content/RoutingContentService.java +++ b/source/java/org/alfresco/repo/content/RoutingContentService.java @@ -31,6 +31,7 @@ import org.alfresco.repo.content.ContentServicePolicies.OnContentUpdatePolicy; import org.alfresco.repo.content.filestore.FileContentStore; import org.alfresco.repo.content.transform.ContentTransformer; import org.alfresco.repo.content.transform.ContentTransformerRegistry; +import org.alfresco.repo.content.transform.magick.ImageMagickContentTransformer; import org.alfresco.repo.policy.ClassPolicyDelegate; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; @@ -78,6 +79,7 @@ public class RoutingContentService implements ContentService private ContentStore store; /** the store for all temporarily created content */ private ContentStore tempStore; + private ImageMagickContentTransformer imageMagickContentTransformer; /** * The policy component @@ -133,6 +135,11 @@ public class RoutingContentService implements ContentService this.avmService = service; } + public void setImageMagickContentTransformer(ImageMagickContentTransformer imageMagickContentTransformer) + { + this.imageMagickContentTransformer = imageMagickContentTransformer; + } + /** * Service initialise */ @@ -407,6 +414,14 @@ public class RoutingContentService implements ContentService return transformer; } + /** + * @see org.alfresco.service.cmr.repository.ContentService#getImageTransformer() + */ + public ContentTransformer getImageTransformer() + { + return imageMagickContentTransformer; + } + /** * @see org.alfresco.repo.content.transform.ContentTransformerRegistry * @see org.alfresco.repo.content.transform.ContentTransformer diff --git a/source/java/org/alfresco/repo/content/RoutingContentServiceTest.java b/source/java/org/alfresco/repo/content/RoutingContentServiceTest.java index cc89d19e92..99cc4e9b8c 100644 --- a/source/java/org/alfresco/repo/content/RoutingContentServiceTest.java +++ b/source/java/org/alfresco/repo/content/RoutingContentServiceTest.java @@ -23,6 +23,8 @@ import java.io.OutputStream; import javax.transaction.RollbackException; import javax.transaction.UserTransaction; +import junit.framework.TestCase; + import org.alfresco.model.ContentModel; import org.alfresco.repo.content.filestore.FileContentWriter; import org.alfresco.repo.content.transform.ContentTransformer; @@ -43,18 +45,21 @@ import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.service.transaction.TransactionService; -import org.alfresco.util.BaseSpringTest; +import org.alfresco.util.ApplicationContextHelper; import org.alfresco.util.GUID; import org.alfresco.util.PropertyMap; import org.alfresco.util.TempFileProvider; +import org.springframework.context.ApplicationContext; /** * @see org.alfresco.repo.content.RoutingContentService * * @author Derek Hulley */ -public class RoutingContentServiceTest extends BaseSpringTest +public class RoutingContentServiceTest extends TestCase { + private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); + private static final String SOME_CONTENT = "ABC"; private static final String TEST_NAMESPACE = "http://www.alfresco.org/test/RoutingContentServiceTest"; @@ -62,24 +67,30 @@ public class RoutingContentServiceTest extends BaseSpringTest private ContentService contentService; private PolicyComponent policyComponent; private NodeService nodeService; + private AuthenticationComponent authenticationComponent; + private UserTransaction txn; private NodeRef rootNodeRef; private NodeRef contentNodeRef; - private AuthenticationComponent authenticationComponent; public RoutingContentServiceTest() { } @Override - public void onSetUpInTransaction() throws Exception + public void setUp() throws Exception { - super.onSetUpInTransaction(); - nodeService = (NodeService) applicationContext.getBean("dbNodeService"); - contentService = (ContentService) applicationContext.getBean(ServiceRegistry.CONTENT_SERVICE.getLocalName()); - this.policyComponent = (PolicyComponent)this.applicationContext.getBean("policyComponent"); - this.authenticationComponent = (AuthenticationComponent)this.applicationContext.getBean("authenticationComponent"); + nodeService = (NodeService) ctx.getBean("dbNodeService"); + contentService = (ContentService) ctx.getBean(ServiceRegistry.CONTENT_SERVICE.getLocalName()); + this.policyComponent = (PolicyComponent) ctx.getBean("policyComponent"); + this.authenticationComponent = (AuthenticationComponent) ctx.getBean("authenticationComponent"); + // authenticate this.authenticationComponent.setSystemUserAsCurrentUser(); + + // start the transaction + txn = getUserTransaction(); + txn.begin(); + // create a store and get the root node StoreRef storeRef = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, getName()); if (!nodeService.exists(storeRef)) @@ -103,7 +114,7 @@ public class RoutingContentServiceTest extends BaseSpringTest } @Override - protected void onTearDownInTransaction() throws Exception + public void tearDown() throws Exception { try { @@ -113,12 +124,22 @@ public class RoutingContentServiceTest extends BaseSpringTest { // ignore } - super.onTearDownInTransaction(); + try + { + if (txn != null) + { + txn.rollback(); + } + } + catch (Throwable e) + { + // ignore + } } private UserTransaction getUserTransaction() { - TransactionService transactionService = (TransactionService)applicationContext.getBean("transactionComponent"); + TransactionService transactionService = (TransactionService) ctx.getBean("transactionComponent"); return (UserTransaction) transactionService.getUserTransaction(); } @@ -236,8 +257,8 @@ public class RoutingContentServiceTest extends BaseSpringTest assertFalse("Reader should indicate that content is missing", reader.exists()); // check the indexing doesn't spank everthing - setComplete(); - endTransaction(); + txn.commit(); + txn = null; } /** @@ -405,8 +426,8 @@ public class RoutingContentServiceTest extends BaseSpringTest public void testConcurrentWritesNoTxn() throws Exception { // ensure that the transaction is ended - ofcourse, we need to force a commit - setComplete(); - endTransaction(); + txn.commit(); + txn = null; ContentWriter writer1 = contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, true); ContentWriter writer2 = contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, true); @@ -425,8 +446,8 @@ public class RoutingContentServiceTest extends BaseSpringTest public void testConcurrentWritesWithSingleTxn() throws Exception { // want to operate in a user transaction - setComplete(); - endTransaction(); + txn.commit(); + txn = null; UserTransaction txn = getUserTransaction(); txn.begin(); @@ -472,8 +493,8 @@ public class RoutingContentServiceTest extends BaseSpringTest public synchronized void testConcurrentWritesWithMultipleTxns() throws Exception { // commit node so that threads can see node - setComplete(); - endTransaction(); + txn.commit(); + txn = null; UserTransaction txn = getUserTransaction(); txn.begin(); @@ -527,8 +548,8 @@ public class RoutingContentServiceTest extends BaseSpringTest public void testTransformation() throws Exception { // commit node so that threads can see node - setComplete(); - endTransaction(); + txn.commit(); + txn = null; UserTransaction txn = getUserTransaction(); txn.begin(); @@ -655,4 +676,27 @@ public class RoutingContentServiceTest extends BaseSpringTest } } } + + /** + * Check that the system is able to handle the uploading of content with an unknown mimetype. + * The unknown mimetype should be preserved, but treated just like an octet stream. + */ + public void testUnknownMimetype() throws Exception + { + String bogusMimetype = "text/bamboozle"; + // get a writer onto the node + ContentWriter writer = contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, true); + writer.setMimetype(bogusMimetype); + + // write something in + writer.putContent(SOME_CONTENT); + + // commit the transaction to ensure that it goes in OK + txn.commit(); + + // so far, so good + ContentReader reader = contentService.getReader(contentNodeRef, ContentModel.PROP_CONTENT); + assertNotNull("Should be able to get reader", reader); + assertEquals("Unknown mimetype was changed", bogusMimetype, reader.getMimetype()); + } } diff --git a/source/java/org/alfresco/repo/content/filestore/FileIOTest.java b/source/java/org/alfresco/repo/content/filestore/FileIOTest.java index 0d0a4dd6d9..a47a1542f6 100644 --- a/source/java/org/alfresco/repo/content/filestore/FileIOTest.java +++ b/source/java/org/alfresco/repo/content/filestore/FileIOTest.java @@ -22,6 +22,9 @@ import java.io.FileOutputStream; import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; +import java.util.HashSet; +import java.util.Set; +import java.util.zip.CRC32; import junit.framework.TestCase; @@ -111,4 +114,30 @@ public class FileIOTest extends TestCase countRead = channelRead.read(bufferRead); assertEquals("Expected full read", 26, countRead); } + + public void testCrcPerformance() throws Exception + { + long before = System.nanoTime(); + int count = 1000000; + Set results = new HashSet(count); + boolean negatives = false; + for (int i = 0; i < count; i++) + { + CRC32 crc = new CRC32(); + crc.update(Integer.toString(i).getBytes()); + long value = crc.getValue(); + if (value < 0) + { + negatives = true; + } + if (!results.add(value)) + { + System.out.println("Duplicate on " + i); + } + } + long after = System.nanoTime(); + long delta = after - before; + double aveNs = (double)delta / (double)count; + System.out.println(String.format("CRC32: %10.2f ns per item. Negatives=" + negatives, aveNs)); + } } diff --git a/source/java/org/alfresco/repo/content/metadata/AbstractMetadataExtracter.java b/source/java/org/alfresco/repo/content/metadata/AbstractMetadataExtracter.java index 50548b8089..5ae16fc2ad 100644 --- a/source/java/org/alfresco/repo/content/metadata/AbstractMetadataExtracter.java +++ b/source/java/org/alfresco/repo/content/metadata/AbstractMetadataExtracter.java @@ -1,220 +1,220 @@ -/* - * Copyright (C) 2005 Jesper Steen M�ller - * - * Licensed under the Mozilla Public License version 1.1 - * with a permitted attribution clause. You may obtain a - * copy of the License at - * - * http://www.alfresco.org/legal/license.txt - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - * either express or implied. See the License for the specific - * language governing permissions and limitations under the - * License. - */ -package org.alfresco.repo.content.metadata; - -import java.io.Serializable; -import java.util.Collections; -import java.util.Map; -import java.util.Set; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.service.cmr.repository.ContentIOException; -import org.alfresco.service.cmr.repository.ContentReader; -import org.alfresco.service.cmr.repository.MimetypeService; -import org.alfresco.service.namespace.QName; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * - * @author Jesper Steen Møller - */ -abstract public class AbstractMetadataExtracter implements MetadataExtracter -{ - protected static Log logger = LogFactory.getLog(AbstractMetadataExtracter.class); - - private MimetypeService mimetypeService; - private MetadataExtracterRegistry registry; - private Set supportedMimetypes; - private double reliability; - private long extractionTime; - - protected AbstractMetadataExtracter(String supportedMimetype, double reliability, long extractionTime) - { - this.supportedMimetypes = Collections.singleton(supportedMimetype); - this.reliability = reliability; - this.extractionTime = extractionTime; - } - - protected AbstractMetadataExtracter(Set supportedMimetypes, double reliability, long extractionTime) - { - this.supportedMimetypes = supportedMimetypes; - this.reliability = reliability; - this.extractionTime = extractionTime; - } - - /** - * Set the registry to register with - * - * @param registry a metadata extracter registry - */ - public void setRegistry(MetadataExtracterRegistry registry) - { - this.registry = registry; - } - - /** - * Helper setter of the mimetype service. This is not always required. - * - * @param mimetypeService - */ - public void setMimetypeService(MimetypeService mimetypeService) - { - this.mimetypeService = mimetypeService; - } - - /** - * @return Returns the mimetype helper - */ - protected MimetypeService getMimetypeService() - { - return mimetypeService; - } - - /** - * Registers this instance of the extracter with the registry. - * - * @see #setRegistry(MetadataExtracterRegistry) - */ - public void register() - { - if (registry == null) - { - logger.warn("Property 'registry' has not been set. Ignoring auto-registration: \n" + - " extracter: " + this); - return; - } - registry.register(this); - } - - /** - * Default reliability check that returns the reliability as configured by the contstructor - * if the mimetype is in the list of supported mimetypes. - * - * @param mimetype the mimetype to check - */ - public double getReliability(String mimetype) - { - if (supportedMimetypes.contains(mimetype)) - return reliability; - else - return 0.0; - } - - public long getExtractionTime() - { - return extractionTime; - } - - /** - * Checks if the mimetype is supported. - * - * @param reader the reader to check - * @throws AlfrescoRuntimeException if the mimetype is not supported - */ - protected void checkReliability(ContentReader reader) - { - String mimetype = reader.getMimetype(); - if (getReliability(mimetype) <= 0.0) - { - throw new AlfrescoRuntimeException( - "Metadata extracter does not support mimetype: \n" + - " reader: " + reader + "\n" + - " supported: " + supportedMimetypes + "\n" + - " extracter: " + this); - } - } - - public final void extract(ContentReader reader, Map destination) throws ContentIOException - { - // check the reliability - checkReliability(reader); - - try - { - extractInternal(reader, destination); - } - catch (Throwable e) - { - throw new ContentIOException("Metadata extraction failed: \n" + - " reader: " + reader, - e); - } - finally - { - // check that the reader was closed - if (!reader.isClosed()) - { - logger.error("Content reader not closed by metadata extracter: \n" + - " reader: " + reader + "\n" + - " extracter: " + this); - } - } - - // done - if (logger.isDebugEnabled()) - { - logger.debug("Completed metadata extraction: \n" + - " reader: " + reader + "\n" + - " extracter: " + this); - } - } - - /** - * Override to provide the necessary extraction logic. Implementations must ensure that the reader - * is closed before the method exits. - * - * @param reader the source of the content - * @param destination the property map to fill - * @throws Throwable an exception - */ - protected abstract void extractInternal(ContentReader reader, Map destination) throws Throwable; - - /** - * Examines a value or string for nulls and adds it to the map (if - * non-empty) - * - * @param prop Alfresco's ContentModel.PROP_ to set. - * @param value Value to set it to - * @param destination Map into which to set it - * @return true, if set, false otherwise - */ - protected boolean trimPut(QName prop, Object value, Map destination) - { - if (value == null) - return false; - if (value instanceof String) - { - String svalue = ((String) value).trim(); - if (svalue.length() > 0) - { - destination.put(prop, svalue); - return true; - } - return false; - } - else if (value instanceof Serializable) - { - destination.put(prop, (Serializable) value); - } - else - { - destination.put(prop, value.toString()); - } - return true; - } -} +/* + * Copyright (C) 2005 Jesper Steen Møller + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.content.metadata; + +import java.io.Serializable; +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.service.cmr.repository.ContentIOException; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.MimetypeService; +import org.alfresco.service.namespace.QName; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * + * @author Jesper Steen Møller + */ +abstract public class AbstractMetadataExtracter implements MetadataExtracter +{ + protected static Log logger = LogFactory.getLog(AbstractMetadataExtracter.class); + + private MimetypeService mimetypeService; + private MetadataExtracterRegistry registry; + private Set supportedMimetypes; + private double reliability; + private long extractionTime; + + protected AbstractMetadataExtracter(String supportedMimetype, double reliability, long extractionTime) + { + this.supportedMimetypes = Collections.singleton(supportedMimetype); + this.reliability = reliability; + this.extractionTime = extractionTime; + } + + protected AbstractMetadataExtracter(Set supportedMimetypes, double reliability, long extractionTime) + { + this.supportedMimetypes = supportedMimetypes; + this.reliability = reliability; + this.extractionTime = extractionTime; + } + + /** + * Set the registry to register with + * + * @param registry a metadata extracter registry + */ + public void setRegistry(MetadataExtracterRegistry registry) + { + this.registry = registry; + } + + /** + * Helper setter of the mimetype service. This is not always required. + * + * @param mimetypeService + */ + public void setMimetypeService(MimetypeService mimetypeService) + { + this.mimetypeService = mimetypeService; + } + + /** + * @return Returns the mimetype helper + */ + protected MimetypeService getMimetypeService() + { + return mimetypeService; + } + + /** + * Registers this instance of the extracter with the registry. + * + * @see #setRegistry(MetadataExtracterRegistry) + */ + public void register() + { + if (registry == null) + { + logger.warn("Property 'registry' has not been set. Ignoring auto-registration: \n" + + " extracter: " + this); + return; + } + registry.register(this); + } + + /** + * Default reliability check that returns the reliability as configured by the contstructor + * if the mimetype is in the list of supported mimetypes. + * + * @param mimetype the mimetype to check + */ + public double getReliability(String mimetype) + { + if (supportedMimetypes.contains(mimetype)) + return reliability; + else + return 0.0; + } + + public long getExtractionTime() + { + return extractionTime; + } + + /** + * Checks if the mimetype is supported. + * + * @param reader the reader to check + * @throws AlfrescoRuntimeException if the mimetype is not supported + */ + protected void checkReliability(ContentReader reader) + { + String mimetype = reader.getMimetype(); + if (getReliability(mimetype) <= 0.0) + { + throw new AlfrescoRuntimeException( + "Metadata extracter does not support mimetype: \n" + + " reader: " + reader + "\n" + + " supported: " + supportedMimetypes + "\n" + + " extracter: " + this); + } + } + + public final void extract(ContentReader reader, Map destination) throws ContentIOException + { + // check the reliability + checkReliability(reader); + + try + { + extractInternal(reader, destination); + } + catch (Throwable e) + { + throw new ContentIOException("Metadata extraction failed: \n" + + " reader: " + reader, + e); + } + finally + { + // check that the reader was closed + if (!reader.isClosed()) + { + logger.error("Content reader not closed by metadata extracter: \n" + + " reader: " + reader + "\n" + + " extracter: " + this); + } + } + + // done + if (logger.isDebugEnabled()) + { + logger.debug("Completed metadata extraction: \n" + + " reader: " + reader + "\n" + + " extracter: " + this); + } + } + + /** + * Override to provide the necessary extraction logic. Implementations must ensure that the reader + * is closed before the method exits. + * + * @param reader the source of the content + * @param destination the property map to fill + * @throws Throwable an exception + */ + protected abstract void extractInternal(ContentReader reader, Map destination) throws Throwable; + + /** + * Examines a value or string for nulls and adds it to the map (if + * non-empty) + * + * @param prop Alfresco's ContentModel.PROP_ to set. + * @param value Value to set it to + * @param destination Map into which to set it + * @return true, if set, false otherwise + */ + protected boolean trimPut(QName prop, Object value, Map destination) + { + if (value == null) + return false; + if (value instanceof String) + { + String svalue = ((String) value).trim(); + if (svalue.length() > 0) + { + destination.put(prop, svalue); + return true; + } + return false; + } + else if (value instanceof Serializable) + { + destination.put(prop, (Serializable) value); + } + else + { + destination.put(prop, value.toString()); + } + return true; + } +} diff --git a/source/java/org/alfresco/repo/content/metadata/AbstractMetadataExtracterTest.java b/source/java/org/alfresco/repo/content/metadata/AbstractMetadataExtracterTest.java index bbb17d5153..a49d7ecb5c 100644 --- a/source/java/org/alfresco/repo/content/metadata/AbstractMetadataExtracterTest.java +++ b/source/java/org/alfresco/repo/content/metadata/AbstractMetadataExtracterTest.java @@ -1,116 +1,116 @@ -/* - * Copyright (C) 2005 Jesper Steen M�ller - * - * Licensed under the Mozilla Public License version 1.1 - * with a permitted attribution clause. You may obtain a - * copy of the License at - * - * http://www.alfresco.org/legal/license.txt - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - * either express or implied. See the License for the specific - * language governing permissions and limitations under the - * License. - */ -package org.alfresco.repo.content.metadata; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.Serializable; -import java.util.HashMap; -import java.util.Map; - -import junit.framework.TestCase; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.content.MimetypeMap; -import org.alfresco.repo.content.filestore.FileContentReader; -import org.alfresco.repo.content.transform.AbstractContentTransformerTest; -import org.alfresco.service.cmr.repository.ContentReader; -import org.alfresco.service.namespace.QName; -import org.alfresco.util.ApplicationContextHelper; -import org.alfresco.util.TempFileProvider; -import org.springframework.context.ApplicationContext; - -/** - * @see org.alfresco.repo.content.metadata.MetadataExtracter - * @see org.alfresco.repo.content.metadata.AbstractMetadataExtracter - * - * @author Jesper Steen Møller - */ -public abstract class AbstractMetadataExtracterTest extends TestCase -{ - private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); - - protected static final String QUICK_TITLE = "The quick brown fox jumps over the lazy dog"; - protected static final String QUICK_DESCRIPTION = "Gym class featuring a brown fox and lazy dog"; - protected static final String QUICK_CREATOR = "Nevin Nollop"; - - protected MimetypeMap mimetypeMap; - - protected abstract MetadataExtracter getExtracter(); - - /** - * Ensures that the temp locations are cleaned out before the tests start - */ - @Override - public void setUp() throws Exception - { - this.mimetypeMap = (MimetypeMap) ctx.getBean("mimetypeService"); - - // perform a little cleaning up - long now = System.currentTimeMillis(); - TempFileProvider.TempFileCleanerJob.removeFiles(now); - } - - /** - * Check that all objects are present - */ - public void testSetUp() throws Exception - { - assertNotNull("MimetypeMap not present", mimetypeMap); - // check that the quick resources are available - File sourceFile = AbstractContentTransformerTest.loadQuickTestFile("txt"); - assertNotNull("quick.* files should be available from Tests", sourceFile); - } - - protected void testExtractFromMimetype(String mimetype) throws Exception - { - Map properties = extractFromMimetype(mimetype); - // check - testCommonMetadata(mimetype, properties); - } - - protected Map extractFromMimetype(String mimetype) throws Exception - { - Map properties = new HashMap(); - - // get the extension for the mimetype - String ext = mimetypeMap.getExtension(mimetype); - - // attempt to get a source file for each mimetype - File sourceFile = AbstractContentTransformerTest.loadQuickTestFile(ext); - if (sourceFile == null) - { - throw new FileNotFoundException("No quick." + ext + " file found for test"); - } - - // construct a reader onto the source file - ContentReader sourceReader = new FileContentReader(sourceFile); - sourceReader.setMimetype(mimetype); - getExtracter().extract(sourceReader, properties); - return properties; - } - - protected void testCommonMetadata(String mimetype, Map properties) - { - assertEquals( - "Property " + ContentModel.PROP_TITLE + " not found for mimetype " + mimetype, - QUICK_TITLE, properties.get(ContentModel.PROP_TITLE)); - assertEquals( - "Property " + ContentModel.PROP_DESCRIPTION + " not found for mimetype " + mimetype, - QUICK_DESCRIPTION, properties.get(ContentModel.PROP_DESCRIPTION)); - } -} +/* + * Copyright (C) 2005 Jesper Steen Møller + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.content.metadata; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +import junit.framework.TestCase; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.content.filestore.FileContentReader; +import org.alfresco.repo.content.transform.AbstractContentTransformerTest; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.ApplicationContextHelper; +import org.alfresco.util.TempFileProvider; +import org.springframework.context.ApplicationContext; + +/** + * @see org.alfresco.repo.content.metadata.MetadataExtracter + * @see org.alfresco.repo.content.metadata.AbstractMetadataExtracter + * + * @author Jesper Steen Møller + */ +public abstract class AbstractMetadataExtracterTest extends TestCase +{ + private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); + + protected static final String QUICK_TITLE = "The quick brown fox jumps over the lazy dog"; + protected static final String QUICK_DESCRIPTION = "Gym class featuring a brown fox and lazy dog"; + protected static final String QUICK_CREATOR = "Nevin Nollop"; + + protected MimetypeMap mimetypeMap; + + protected abstract MetadataExtracter getExtracter(); + + /** + * Ensures that the temp locations are cleaned out before the tests start + */ + @Override + public void setUp() throws Exception + { + this.mimetypeMap = (MimetypeMap) ctx.getBean("mimetypeService"); + + // perform a little cleaning up + long now = System.currentTimeMillis(); + TempFileProvider.TempFileCleanerJob.removeFiles(now); + } + + /** + * Check that all objects are present + */ + public void testSetUp() throws Exception + { + assertNotNull("MimetypeMap not present", mimetypeMap); + // check that the quick resources are available + File sourceFile = AbstractContentTransformerTest.loadQuickTestFile("txt"); + assertNotNull("quick.* files should be available from Tests", sourceFile); + } + + protected void testExtractFromMimetype(String mimetype) throws Exception + { + Map properties = extractFromMimetype(mimetype); + // check + testCommonMetadata(mimetype, properties); + } + + protected Map extractFromMimetype(String mimetype) throws Exception + { + Map properties = new HashMap(); + + // get the extension for the mimetype + String ext = mimetypeMap.getExtension(mimetype); + + // attempt to get a source file for each mimetype + File sourceFile = AbstractContentTransformerTest.loadQuickTestFile(ext); + if (sourceFile == null) + { + throw new FileNotFoundException("No quick." + ext + " file found for test"); + } + + // construct a reader onto the source file + ContentReader sourceReader = new FileContentReader(sourceFile); + sourceReader.setMimetype(mimetype); + getExtracter().extract(sourceReader, properties); + return properties; + } + + protected void testCommonMetadata(String mimetype, Map properties) + { + assertEquals( + "Property " + ContentModel.PROP_TITLE + " not found for mimetype " + mimetype, + QUICK_TITLE, properties.get(ContentModel.PROP_TITLE)); + assertEquals( + "Property " + ContentModel.PROP_DESCRIPTION + " not found for mimetype " + mimetype, + QUICK_DESCRIPTION, properties.get(ContentModel.PROP_DESCRIPTION)); + } +} diff --git a/source/java/org/alfresco/repo/content/metadata/HtmlMetadataExtracter.java b/source/java/org/alfresco/repo/content/metadata/HtmlMetadataExtracter.java index d8c4657c50..a1ea0711cb 100644 --- a/source/java/org/alfresco/repo/content/metadata/HtmlMetadataExtracter.java +++ b/source/java/org/alfresco/repo/content/metadata/HtmlMetadataExtracter.java @@ -1,169 +1,169 @@ -/* - * Copyright (C) 2005 Jesper Steen M�ller - * - * Licensed under the Mozilla Public License version 1.1 - * with a permitted attribution clause. You may obtain a - * copy of the License at - * - * http://www.alfresco.org/legal/license.txt - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - * either express or implied. See the License for the specific - * language governing permissions and limitations under the - * License. - */ -package org.alfresco.repo.content.metadata; - -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; -import java.io.Serializable; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import javax.swing.text.ChangedCharSetException; -import javax.swing.text.MutableAttributeSet; -import javax.swing.text.html.HTML; -import javax.swing.text.html.HTMLEditorKit; -import javax.swing.text.html.parser.ParserDelegator; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.content.MimetypeMap; -import org.alfresco.service.cmr.repository.ContentReader; -import org.alfresco.service.namespace.QName; - -/** - * - * @author Jesper Steen Møller - */ -public class HtmlMetadataExtracter extends AbstractMetadataExtracter -{ - private static final Set MIMETYPES = new HashSet(5); - static - { - MIMETYPES.add(MimetypeMap.MIMETYPE_HTML); - MIMETYPES.add(MimetypeMap.MIMETYPE_XHTML); - } - - public HtmlMetadataExtracter() - { - super(MIMETYPES, 1.0, 1000); - } - - public void extractInternal(ContentReader reader, Map destination) throws Throwable - { - final Map tempDestination = new HashMap(); - - HTMLEditorKit.ParserCallback callback = new HTMLEditorKit.ParserCallback() - { - StringBuffer title = null; - boolean inHead = false; - - public void handleText(char[] data, int pos) - { - if (title != null) - { - title.append(data); - } - } - - public void handleComment(char[] data, int pos) - { - // Perhaps sniff for Office 9+ metadata in here? - } - - public void handleStartTag(HTML.Tag t, MutableAttributeSet a, int pos) - { - if (HTML.Tag.HEAD.equals(t)) - { - inHead = true; - } - else if (HTML.Tag.TITLE.equals(t) && inHead) - { - title = new StringBuffer(); - } - else - handleSimpleTag(t, a, pos); - } - - public void handleEndTag(HTML.Tag t, int pos) - { - if (HTML.Tag.HEAD.equals(t)) - { - inHead = false; - } - else if (HTML.Tag.TITLE.equals(t) && title != null) - { - trimPut(ContentModel.PROP_TITLE, title.toString(), tempDestination); - title = null; - } - } - - public void handleSimpleTag(HTML.Tag t, MutableAttributeSet a, int pos) - { - if (HTML.Tag.META.equals(t)) - { - Object nameO = a.getAttribute(HTML.Attribute.NAME); - Object valueO = a.getAttribute(HTML.Attribute.CONTENT); - if (nameO == null || valueO == null) - return; - - String name = nameO.toString(); - - if (name.equalsIgnoreCase("creator") || name.equalsIgnoreCase("author") - || name.equalsIgnoreCase("dc.creator")) - { - trimPut(ContentModel.PROP_AUTHOR, valueO, tempDestination); - } - if (name.equalsIgnoreCase("description") || name.equalsIgnoreCase("dc.description")) - { - trimPut(ContentModel.PROP_DESCRIPTION, valueO, tempDestination); - } - } - } - - public void handleError(String errorMsg, int pos) - { - } - }; - - String charsetGuess = "UTF-8"; - int tries = 0; - while (tries < 3) - { - tempDestination.clear(); - Reader r = null; - InputStream cis = null; - try - { - cis = reader.getContentInputStream(); - // TODO: for now, use default charset; we should attempt to map from html meta-data - r = new InputStreamReader(cis); - HTMLEditorKit.Parser parser = new ParserDelegator(); - parser.parse(r, callback, tries > 0); - destination.putAll(tempDestination); - break; - } - catch (ChangedCharSetException ccse) - { - tries++; - charsetGuess = ccse.getCharSetSpec(); - int begin = charsetGuess.indexOf("charset="); - if (begin > 0) - charsetGuess = charsetGuess.substring(begin + 8, charsetGuess.length()); - reader = reader.getReader(); - } - finally - { - if (r != null) - r.close(); - if (cis != null) - cis.close(); - } - } - } -} +/* + * Copyright (C) 2005 Jesper Steen Møller + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.content.metadata; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.Serializable; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import javax.swing.text.ChangedCharSetException; +import javax.swing.text.MutableAttributeSet; +import javax.swing.text.html.HTML; +import javax.swing.text.html.HTMLEditorKit; +import javax.swing.text.html.parser.ParserDelegator; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.namespace.QName; + +/** + * + * @author Jesper Steen Møller + */ +public class HtmlMetadataExtracter extends AbstractMetadataExtracter +{ + private static final Set MIMETYPES = new HashSet(5); + static + { + MIMETYPES.add(MimetypeMap.MIMETYPE_HTML); + MIMETYPES.add(MimetypeMap.MIMETYPE_XHTML); + } + + public HtmlMetadataExtracter() + { + super(MIMETYPES, 1.0, 1000); + } + + public void extractInternal(ContentReader reader, Map destination) throws Throwable + { + final Map tempDestination = new HashMap(); + + HTMLEditorKit.ParserCallback callback = new HTMLEditorKit.ParserCallback() + { + StringBuffer title = null; + boolean inHead = false; + + public void handleText(char[] data, int pos) + { + if (title != null) + { + title.append(data); + } + } + + public void handleComment(char[] data, int pos) + { + // Perhaps sniff for Office 9+ metadata in here? + } + + public void handleStartTag(HTML.Tag t, MutableAttributeSet a, int pos) + { + if (HTML.Tag.HEAD.equals(t)) + { + inHead = true; + } + else if (HTML.Tag.TITLE.equals(t) && inHead) + { + title = new StringBuffer(); + } + else + handleSimpleTag(t, a, pos); + } + + public void handleEndTag(HTML.Tag t, int pos) + { + if (HTML.Tag.HEAD.equals(t)) + { + inHead = false; + } + else if (HTML.Tag.TITLE.equals(t) && title != null) + { + trimPut(ContentModel.PROP_TITLE, title.toString(), tempDestination); + title = null; + } + } + + public void handleSimpleTag(HTML.Tag t, MutableAttributeSet a, int pos) + { + if (HTML.Tag.META.equals(t)) + { + Object nameO = a.getAttribute(HTML.Attribute.NAME); + Object valueO = a.getAttribute(HTML.Attribute.CONTENT); + if (nameO == null || valueO == null) + return; + + String name = nameO.toString(); + + if (name.equalsIgnoreCase("creator") || name.equalsIgnoreCase("author") + || name.equalsIgnoreCase("dc.creator")) + { + trimPut(ContentModel.PROP_AUTHOR, valueO, tempDestination); + } + if (name.equalsIgnoreCase("description") || name.equalsIgnoreCase("dc.description")) + { + trimPut(ContentModel.PROP_DESCRIPTION, valueO, tempDestination); + } + } + } + + public void handleError(String errorMsg, int pos) + { + } + }; + + String charsetGuess = "UTF-8"; + int tries = 0; + while (tries < 3) + { + tempDestination.clear(); + Reader r = null; + InputStream cis = null; + try + { + cis = reader.getContentInputStream(); + // TODO: for now, use default charset; we should attempt to map from html meta-data + r = new InputStreamReader(cis); + HTMLEditorKit.Parser parser = new ParserDelegator(); + parser.parse(r, callback, tries > 0); + destination.putAll(tempDestination); + break; + } + catch (ChangedCharSetException ccse) + { + tries++; + charsetGuess = ccse.getCharSetSpec(); + int begin = charsetGuess.indexOf("charset="); + if (begin > 0) + charsetGuess = charsetGuess.substring(begin + 8, charsetGuess.length()); + reader = reader.getReader(); + } + finally + { + if (r != null) + r.close(); + if (cis != null) + cis.close(); + } + } + } +} diff --git a/source/java/org/alfresco/repo/content/metadata/HtmlMetadataExtracterTest.java b/source/java/org/alfresco/repo/content/metadata/HtmlMetadataExtracterTest.java index 986c67a9d4..7867e8df9c 100644 --- a/source/java/org/alfresco/repo/content/metadata/HtmlMetadataExtracterTest.java +++ b/source/java/org/alfresco/repo/content/metadata/HtmlMetadataExtracterTest.java @@ -1,57 +1,57 @@ -/* - * Copyright (C) 2005 Jesper Steen M�ller - * - * Licensed under the Mozilla Public License version 1.1 - * with a permitted attribution clause. You may obtain a - * copy of the License at - * - * http://www.alfresco.org/legal/license.txt - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - * either express or implied. See the License for the specific - * language governing permissions and limitations under the - * License. - */ -package org.alfresco.repo.content.metadata; - -import org.alfresco.repo.content.MimetypeMap; - -/** - * @author Jesper Steen Møller - */ -public class HtmlMetadataExtracterTest extends AbstractMetadataExtracterTest -{ - private MetadataExtracter extracter; - - @Override - public void setUp() throws Exception - { - super.setUp(); - extracter = new HtmlMetadataExtracter(); - } - - /** - * @return Returns the same transformer regardless - it is allowed - */ - protected MetadataExtracter getExtracter() - { - return extracter; - } - - public void testReliability() throws Exception - { - double reliability = 0.0; - reliability = extracter.getReliability(MimetypeMap.MIMETYPE_TEXT_PLAIN); - assertEquals("Mimetype text should not be supported", 0.0, reliability); - - reliability = extracter.getReliability(MimetypeMap.MIMETYPE_HTML); - assertEquals("HTML should be supported", 1.0, reliability); - } - - public void testHtmlExtraction() throws Exception - { - testExtractFromMimetype(MimetypeMap.MIMETYPE_HTML); - } -} +/* + * Copyright (C) 2005 Jesper Steen Møller + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.content.metadata; + +import org.alfresco.repo.content.MimetypeMap; + +/** + * @author Jesper Steen Møller + */ +public class HtmlMetadataExtracterTest extends AbstractMetadataExtracterTest +{ + private MetadataExtracter extracter; + + @Override + public void setUp() throws Exception + { + super.setUp(); + extracter = new HtmlMetadataExtracter(); + } + + /** + * @return Returns the same transformer regardless - it is allowed + */ + protected MetadataExtracter getExtracter() + { + return extracter; + } + + public void testReliability() throws Exception + { + double reliability = 0.0; + reliability = extracter.getReliability(MimetypeMap.MIMETYPE_TEXT_PLAIN); + assertEquals("Mimetype text should not be supported", 0.0, reliability); + + reliability = extracter.getReliability(MimetypeMap.MIMETYPE_HTML); + assertEquals("HTML should be supported", 1.0, reliability); + } + + public void testHtmlExtraction() throws Exception + { + testExtractFromMimetype(MimetypeMap.MIMETYPE_HTML); + } +} diff --git a/source/java/org/alfresco/repo/content/metadata/MailMetadataExtracter.java b/source/java/org/alfresco/repo/content/metadata/MailMetadataExtracter.java index 6f527ece14..73fae3b2e7 100644 --- a/source/java/org/alfresco/repo/content/metadata/MailMetadataExtracter.java +++ b/source/java/org/alfresco/repo/content/metadata/MailMetadataExtracter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005 Jesper Steen M�ller + * Copyright (C) 2005 Alfresco, Inc. * * Licensed under the Mozilla Public License version 1.1 * with a permitted attribution clause. You may obtain a @@ -26,6 +26,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import org.alfresco.model.ContentModel; import org.alfresco.service.cmr.repository.ContentIOException; import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.namespace.NamespaceService; @@ -45,17 +46,8 @@ public class MailMetadataExtracter extends AbstractMetadataExtracter public static String[] SUPPORTED_MIMETYPES = new String[] { "message/rfc822"}; - private static final String SUBSTG_MESSAGEBODY = "__substg1.0_1000001E"; - private static final String SUBSTG_RECIPIENTEMAIL = "__substg1.0_39FE001E"; - private static final String SUBSTG_RECEIVEDEMAIL = "__substg1.0_0076001E"; - private static final String SUBSTG_SENDEREMAIL = "__substg1.0_0C1F001E"; - private static final String SUBSTG_DATE = "__substg1.0_00470102"; - - private static final QName ASPECT_MAILED = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "emailed"); - private static final QName PROP_SENTDATE = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "sentdate"); - private static final QName PROP_ORIGINATOR = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "originator"); - private static final QName PROP_ADDRESSEE = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "addressee"); - private static final QName PROP_ADDRESSEES = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "addressees"); + private static final String STREAM_PREFIX = "__substg1.0_"; + private static final int STREAM_PREFIX_LENGTH = STREAM_PREFIX.length(); // the CC: email addresses private ThreadLocal> receipientEmails = new ThreadLocal>(); @@ -73,47 +65,10 @@ public class MailMetadataExtracter extends AbstractMetadataExtracter { try { - String name = event.getName(); - - if (name.equals(SUBSTG_RECIPIENTEMAIL)) // a recipient email address + if (event.getName().startsWith(STREAM_PREFIX)) { - String emailAddress = readPlainTextStream(event.getStream()); - receipientEmails.get().add(convertExchangeAddress(emailAddress)); - } - else if (name.equals(SUBSTG_RECEIVEDEMAIL)) // receiver email address - { - String emailAddress = readPlainTextStream(event.getStream()); - destination.put(PROP_ADDRESSEE, convertExchangeAddress(emailAddress)); - } - else if (name.equals(SUBSTG_SENDEREMAIL)) // sender email - NOTE either email OR full Exchange data e.g. : /O=HOSTEDSERVICE2/OU=FIRST ADMINISTRATIVE GROUP/CN=RECIPIENTS/CN=MIKE.FARMAN@BEN - { - String emailAddress = readPlainTextStream(event.getStream()); - destination.put(PROP_ORIGINATOR, convertExchangeAddress(emailAddress)); - } - else if (name.equals(SUBSTG_DATE)) - { - // the date is not really plain text - but it's easier to parse as such - String date = readPlainTextStream(event.getStream()); - int valueIndex = date.indexOf("l="); - if (valueIndex != -1) - { - int dateIndex = date.indexOf('-', valueIndex); - if (dateIndex != -1) - { - dateIndex++; - String strYear = date.substring(dateIndex, dateIndex + 2); - int year = Integer.parseInt(strYear) + (2000 - 1900); - String strMonth = date.substring(dateIndex + 2, dateIndex + 4); - int month = Integer.parseInt(strMonth) - 1; - String strDay = date.substring(dateIndex + 4, dateIndex + 6); - int day = Integer.parseInt(strDay); - String strHour = date.substring(dateIndex + 6, dateIndex + 8); - int hour = Integer.parseInt(strHour); - String strMinute = date.substring(dateIndex + 10, dateIndex + 12); - int minute = Integer.parseInt(strMinute); - destination.put(PROP_SENTDATE, new Date(year, month, day, hour, minute)); - } - } + StreamHandler handler = new StreamHandler(event.getName(), event.getStream()); + handler.process(destination); } } catch (Exception ex) @@ -145,7 +100,7 @@ public class MailMetadataExtracter extends AbstractMetadataExtracter // store multi-value extracted property if (receipientEmails.get().size() != 0) { - destination.put(PROP_ADDRESSEES, (Serializable)receipientEmails.get()); + destination.put(ContentModel.PROP_ADDRESSEES, (Serializable)receipientEmails.get()); } } finally @@ -157,14 +112,6 @@ public class MailMetadataExtracter extends AbstractMetadataExtracter } } - private static String readPlainTextStream(DocumentInputStream stream) - throws IOException - { - byte[] data = new byte[stream.available()]; - int read = stream.read(data); - return new String(data); - } - private static String convertExchangeAddress(String email) { if (email.lastIndexOf("/CN=") == -1) @@ -177,4 +124,111 @@ public class MailMetadataExtracter extends AbstractMetadataExtracter return email.substring(email.lastIndexOf("/CN=") + 4); } } + + private static final String ENCODING_TEXT = "001E"; + private static final String ENCODING_BINARY = "0102"; + private static final String ENCODING_UNICODE = "001F"; + + private static final String SUBSTG_MESSAGEBODY = "1000"; + private static final String SUBSTG_RECIPIENTEMAIL = "39FE"; + private static final String SUBSTG_RECEIVEDEMAIL = "0076"; + private static final String SUBSTG_SENDEREMAIL = "0C1F"; + private static final String SUBSTG_DATE = "0047"; + private static final String SUBSTG_SUBJECT = "0037"; + + /** + * Class to handle stream types. Can process and extract specific streams. + */ + private class StreamHandler + { + StreamHandler(String name, DocumentInputStream stream) + { + this.type = name.substring(STREAM_PREFIX_LENGTH, STREAM_PREFIX_LENGTH + 4); + this.encoding = name.substring(STREAM_PREFIX_LENGTH + 4, STREAM_PREFIX_LENGTH + 8); + this.stream = stream; + } + + void process(final Map destination) + throws IOException + { + if (type.equals(SUBSTG_SENDEREMAIL)) + { + destination.put(ContentModel.PROP_ORIGINATOR, convertExchangeAddress(extractText())); + } + else if (type.equals(SUBSTG_RECIPIENTEMAIL)) + { + receipientEmails.get().add(convertExchangeAddress(extractText())); + } + else if (type.equals(SUBSTG_RECEIVEDEMAIL)) + { + destination.put(ContentModel.PROP_ADDRESSEE, convertExchangeAddress(extractText())); + } + else if (type.equals(SUBSTG_SUBJECT)) + { + destination.put(ContentModel.PROP_SUBJECT, extractText()); + } + else if (type.equals(SUBSTG_DATE)) + { + // the date is not really plain text - but it's easier to parse as such + String date = extractText(); + int valueIndex = date.indexOf("l="); + if (valueIndex != -1) + { + int dateIndex = date.indexOf('-', valueIndex); + if (dateIndex != -1) + { + dateIndex++; + String strYear = date.substring(dateIndex, dateIndex + 2); + int year = Integer.parseInt(strYear) + (2000 - 1900); + String strMonth = date.substring(dateIndex + 2, dateIndex + 4); + int month = Integer.parseInt(strMonth) - 1; + String strDay = date.substring(dateIndex + 4, dateIndex + 6); + int day = Integer.parseInt(strDay); + String strHour = date.substring(dateIndex + 6, dateIndex + 8); + int hour = Integer.parseInt(strHour); + String strMinute = date.substring(dateIndex + 10, dateIndex + 12); + int minute = Integer.parseInt(strMinute); + destination.put(ContentModel.PROP_SENTDATE, new Date(year, month, day, hour, minute)); + } + } + } + } + + /** + * Extract the text from the stream based on the encoding + * + * @return String + * + * @throws IOException + */ + private String extractText() + throws IOException + { + byte[] data = new byte[stream.available()]; + stream.read(data); + + if (this.encoding.equals(ENCODING_TEXT) || this.encoding.equals(ENCODING_BINARY)) + { + return new String(data); + } + else if (this.encoding.equals(ENCODING_UNICODE)) + { + // convert double-byte encoding to single byte for String conversion + byte[] b = new byte[data.length >> 1]; + for (int i=0; i - * - * @param sourceMimetype the source mimetype - * @return Returns a score 0.0 to 1.0. 0.0 indicates that the extraction - * cannot be performed at all. 1.0 indicates that the extraction can - * be performed perfectly. - */ - public double getReliability(String sourceMimetype); - - /** - * Provides an estimate, usually a worst case guess, of how long an - * extraction will take. - *

- * This method is used to determine, up front, which of a set of equally - * reliant transformers will be used for a specific extraction. - * - * @return Returns the approximate number of milliseconds per transformation - */ - public long getExtractionTime(); - - /** - * Extracts the metadata from the content provided by the reader and source - * mimetype to the supplied map. - *

- * The extraction viability can be determined by an up front call to - * {@link #getReliability(String)}. - *

- * The source mimetype must be available on the - * {@link org.alfresco.service.cmr.repository.ContentAccessor#getMimetype()} method - * of the reader. - * - * @param reader the source of the content - * @param destination the destination of the extraction - * @throws ContentIOException if an IO exception occurs - */ - public void extract(ContentReader reader, Map destination) throws ContentIOException; - -} +/* + * Copyright (C) 2005 Jesper Steen Møller + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.content.metadata; + +import java.io.Serializable; +import java.util.Map; + +import org.alfresco.service.cmr.repository.ContentIOException; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.namespace.QName; + +/** + * + * @author Jesper Steen Møller + */ +public interface MetadataExtracter +{ + /** + * Provides the approximate accuracy with which this extracter can extract + * metadata for the mimetype. + *

+ * + * @param sourceMimetype the source mimetype + * @return Returns a score 0.0 to 1.0. 0.0 indicates that the extraction + * cannot be performed at all. 1.0 indicates that the extraction can + * be performed perfectly. + */ + public double getReliability(String sourceMimetype); + + /** + * Provides an estimate, usually a worst case guess, of how long an + * extraction will take. + *

+ * This method is used to determine, up front, which of a set of equally + * reliant transformers will be used for a specific extraction. + * + * @return Returns the approximate number of milliseconds per transformation + */ + public long getExtractionTime(); + + /** + * Extracts the metadata from the content provided by the reader and source + * mimetype to the supplied map. + *

+ * The extraction viability can be determined by an up front call to + * {@link #getReliability(String)}. + *

+ * The source mimetype must be available on the + * {@link org.alfresco.service.cmr.repository.ContentAccessor#getMimetype()} method + * of the reader. + * + * @param reader the source of the content + * @param destination the destination of the extraction + * @throws ContentIOException if an IO exception occurs + */ + public void extract(ContentReader reader, Map destination) throws ContentIOException; + +} diff --git a/source/java/org/alfresco/repo/content/metadata/MetadataExtracterRegistry.java b/source/java/org/alfresco/repo/content/metadata/MetadataExtracterRegistry.java index 8dd87fb63b..e8302e9e76 100644 --- a/source/java/org/alfresco/repo/content/metadata/MetadataExtracterRegistry.java +++ b/source/java/org/alfresco/repo/content/metadata/MetadataExtracterRegistry.java @@ -1,191 +1,172 @@ -/* - * Copyright (C) 2005 Jesper Steen M�ller - * - * Licensed under the Mozilla Public License version 1.1 - * with a permitted attribution clause. You may obtain a - * copy of the License at - * - * http://www.alfresco.org/legal/license.txt - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - * either express or implied. See the License for the specific - * language governing permissions and limitations under the - * License. - */ -package org.alfresco.repo.content.metadata; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.repo.content.MimetypeMap; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * Holds and provides the most appropriate metadate extracter for a particular - * mimetype. - *

- * The extracters themselves know how well they are able to extract metadata. - * - * @see org.alfresco.repo.content.metadata.MetadataExtracter - * @author Jesper Steen Møller - */ -public class MetadataExtracterRegistry -{ - private static final Log logger = LogFactory.getLog(MetadataExtracterRegistry.class); - - private List extracters; - private Map extracterCache; - - private MimetypeMap mimetypeMap; - /** Controls read access to the cache */ - private Lock extracterCacheReadLock; - /** controls write access to the cache */ - private Lock extracterCacheWriteLock; - - public MetadataExtracterRegistry() - { - // initialise lists - extracters = new ArrayList(10); - extracterCache = new HashMap(17); - - // create lock objects for access to the cache - ReadWriteLock extractionCacheLock = new ReentrantReadWriteLock(); - extracterCacheReadLock = extractionCacheLock.readLock(); - extracterCacheWriteLock = extractionCacheLock.writeLock(); - } - - /** - * The mimetype map that will be used to check requests against - * - * @param mimetypeMap a map of mimetypes - */ - public void setMimetypeMap(MimetypeMap mimetypeMap) - { - this.mimetypeMap = mimetypeMap; - } - - /** - * Register an instance of an extracter for use - * - * @param extracter an extracter - */ - public void register(MetadataExtracter extracter) - { - if (logger.isDebugEnabled()) - { - logger.debug("Registering metadata extracter: " + extracter); - } - - extracterCacheWriteLock.lock(); - try - { - extracters.add(extracter); - extracterCache.clear(); - } - finally - { - extracterCacheWriteLock.unlock(); - } - } - - /** - * Gets the best metadata extracter. This is a combination of the most - * reliable and the most performant extracter. - *

- * The result is cached for quicker access next time. - * - * @param mimetype the source MIME of the extraction - * @return Returns a metadata extracter that can extract metadata from the - * chosen MIME type. - */ - public MetadataExtracter getExtracter(String sourceMimetype) - { - // check that the mimetypes are valid - if (!mimetypeMap.getMimetypes().contains(sourceMimetype)) - { - throw new AlfrescoRuntimeException("Unknown extraction source mimetype: " + sourceMimetype); - } - - MetadataExtracter extracter = null; - extracterCacheReadLock.lock(); - try - { - if (extracterCache.containsKey(sourceMimetype)) - { - // the translation has been requested before - // it might have been null - return extracterCache.get(sourceMimetype); - } - } - finally - { - extracterCacheReadLock.unlock(); - } - - // the translation has not been requested before - // get a write lock on the cache - // no double check done as it is not an expensive task - extracterCacheWriteLock.lock(); - try - { - // find the most suitable transformer - may be empty list - extracter = findBestExtracter(sourceMimetype); - // store the result even if it is null - extracterCache.put(sourceMimetype, extracter); - return extracter; - } - finally - { - extracterCacheWriteLock.unlock(); - } - } - - /** - * @param sourceMimetype The MIME type under examination - * @return The fastest of the most reliable extracters in extracters - * for the given MIME type, or null if none is available. - */ - private MetadataExtracter findBestExtracter(String sourceMimetype) - { - double bestReliability = -1; - long bestTime = Long.MAX_VALUE; - logger.debug("Finding best extracter for " + sourceMimetype); - - MetadataExtracter bestExtracter = null; - - for (MetadataExtracter ext : extracters) - { - double r = ext.getReliability(sourceMimetype); - if (r <= 0.0) - { - // extraction not achievable - continue; - } - else if (r == bestReliability) - { - long time = ext.getExtractionTime(); - if (time < bestTime) - { - bestExtracter = ext; - bestTime = time; - } - } - else if (r > bestReliability) - { - bestExtracter = ext; - bestReliability = r; - bestTime = ext.getExtractionTime(); - } - } - return bestExtracter; - } +/* + * Copyright (C) 2005 Jesper Steen Møller + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.content.metadata; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Holds and provides the most appropriate metadate extracter for a particular + * mimetype. + *

+ * The extracters themselves know how well they are able to extract metadata. + * + * @see org.alfresco.repo.content.metadata.MetadataExtracter + * @author Jesper Steen Møller + */ +public class MetadataExtracterRegistry +{ + private static final Log logger = LogFactory.getLog(MetadataExtracterRegistry.class); + + private List extracters; + private Map extracterCache; + + /** Controls read access to the cache */ + private Lock extracterCacheReadLock; + /** controls write access to the cache */ + private Lock extracterCacheWriteLock; + + public MetadataExtracterRegistry() + { + // initialise lists + extracters = new ArrayList(10); + extracterCache = new HashMap(17); + + // create lock objects for access to the cache + ReadWriteLock extractionCacheLock = new ReentrantReadWriteLock(); + extracterCacheReadLock = extractionCacheLock.readLock(); + extracterCacheWriteLock = extractionCacheLock.writeLock(); + } + + /** + * Register an instance of an extracter for use + * + * @param extracter an extracter + */ + public void register(MetadataExtracter extracter) + { + if (logger.isDebugEnabled()) + { + logger.debug("Registering metadata extracter: " + extracter); + } + + extracterCacheWriteLock.lock(); + try + { + extracters.add(extracter); + extracterCache.clear(); + } + finally + { + extracterCacheWriteLock.unlock(); + } + } + + /** + * Gets the best metadata extracter. This is a combination of the most + * reliable and the most performant extracter. + *

+ * The result is cached for quicker access next time. + * + * @param mimetype the source MIME of the extraction + * @return Returns a metadata extracter that can extract metadata from the + * chosen MIME type. + */ + public MetadataExtracter getExtracter(String sourceMimetype) + { + MetadataExtracter extracter = null; + extracterCacheReadLock.lock(); + try + { + if (extracterCache.containsKey(sourceMimetype)) + { + // the translation has been requested before + // it might have been null + return extracterCache.get(sourceMimetype); + } + } + finally + { + extracterCacheReadLock.unlock(); + } + + // the translation has not been requested before + // get a write lock on the cache + // no double check done as it is not an expensive task + extracterCacheWriteLock.lock(); + try + { + // find the most suitable transformer - may be empty list + extracter = findBestExtracter(sourceMimetype); + // store the result even if it is null + extracterCache.put(sourceMimetype, extracter); + return extracter; + } + finally + { + extracterCacheWriteLock.unlock(); + } + } + + /** + * @param sourceMimetype The MIME type under examination + * @return The fastest of the most reliable extracters in extracters + * for the given MIME type, or null if none is available. + */ + private MetadataExtracter findBestExtracter(String sourceMimetype) + { + double bestReliability = -1; + long bestTime = Long.MAX_VALUE; + logger.debug("Finding best extracter for " + sourceMimetype); + + MetadataExtracter bestExtracter = null; + + for (MetadataExtracter ext : extracters) + { + double r = ext.getReliability(sourceMimetype); + if (r <= 0.0) + { + // extraction not achievable + continue; + } + else if (r == bestReliability) + { + long time = ext.getExtractionTime(); + if (time < bestTime) + { + bestExtracter = ext; + bestTime = time; + } + } + else if (r > bestReliability) + { + bestExtracter = ext; + bestReliability = r; + bestTime = ext.getExtractionTime(); + } + } + return bestExtracter; + } } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/content/metadata/OfficeMetadataExtracter.java b/source/java/org/alfresco/repo/content/metadata/OfficeMetadataExtracter.java index 179be80aa7..bd3af493a3 100644 --- a/source/java/org/alfresco/repo/content/metadata/OfficeMetadataExtracter.java +++ b/source/java/org/alfresco/repo/content/metadata/OfficeMetadataExtracter.java @@ -1,101 +1,101 @@ -/* - * Copyright (C) 2005 Jesper Steen M�ller - * - * Licensed under the Mozilla Public License version 1.1 - * with a permitted attribution clause. You may obtain a - * copy of the License at - * - * http://www.alfresco.org/legal/license.txt - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - * either express or implied. See the License for the specific - * language governing permissions and limitations under the - * License. - */ -package org.alfresco.repo.content.metadata; - -import java.io.IOException; -import java.io.InputStream; -import java.io.Serializable; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Map; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.content.MimetypeMap; -import org.alfresco.service.cmr.repository.ContentIOException; -import org.alfresco.service.cmr.repository.ContentReader; -import org.alfresco.service.namespace.QName; -import org.apache.poi.hpsf.PropertySet; -import org.apache.poi.hpsf.PropertySetFactory; -import org.apache.poi.hpsf.SummaryInformation; -import org.apache.poi.poifs.eventfilesystem.POIFSReader; -import org.apache.poi.poifs.eventfilesystem.POIFSReaderEvent; -import org.apache.poi.poifs.eventfilesystem.POIFSReaderListener; - -/** - * Office file format Metadata Extracter - * - * @author Jesper Steen Møller - */ -public class OfficeMetadataExtracter extends AbstractMetadataExtracter -{ - public static String[] SUPPORTED_MIMETYPES = new String[] { - MimetypeMap.MIMETYPE_WORD, - MimetypeMap.MIMETYPE_EXCEL, - MimetypeMap.MIMETYPE_PPT}; - - public OfficeMetadataExtracter() - { - super(new HashSet(Arrays.asList(SUPPORTED_MIMETYPES)), 1.0, 1000); - } - - public void extractInternal(ContentReader reader, final Map destination) throws Throwable - { - POIFSReaderListener readerListener = new POIFSReaderListener() - { - public void processPOIFSReaderEvent(final POIFSReaderEvent event) - { - try - { - PropertySet ps = PropertySetFactory.create(event.getStream()); - if (ps instanceof SummaryInformation) - { - SummaryInformation si = (SummaryInformation) ps; - - // Titled aspect - trimPut(ContentModel.PROP_TITLE, si.getTitle(), destination); - trimPut(ContentModel.PROP_DESCRIPTION, si.getSubject(), destination); - - // Auditable aspect - trimPut(ContentModel.PROP_CREATED, si.getCreateDateTime(), destination); - trimPut(ContentModel.PROP_MODIFIED, si.getLastSaveDateTime(), destination); - trimPut(ContentModel.PROP_AUTHOR, si.getAuthor(), destination); - } - } - catch (Exception ex) - { - throw new ContentIOException("Property set stream: " + event.getPath() + event.getName(), ex); - } - } - }; - - InputStream is = null; - try - { - is = reader.getContentInputStream(); - POIFSReader poiFSReader = new POIFSReader(); - poiFSReader.registerListener(readerListener, SummaryInformation.DEFAULT_STREAM_NAME); - poiFSReader.read(is); - } - finally - { - if (is != null) - { - try { is.close(); } catch (IOException e) {} - } - } - } -} +/* + * Copyright (C) 2005 Jesper Steen Møller + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.content.metadata; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Serializable; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.service.cmr.repository.ContentIOException; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.namespace.QName; +import org.apache.poi.hpsf.PropertySet; +import org.apache.poi.hpsf.PropertySetFactory; +import org.apache.poi.hpsf.SummaryInformation; +import org.apache.poi.poifs.eventfilesystem.POIFSReader; +import org.apache.poi.poifs.eventfilesystem.POIFSReaderEvent; +import org.apache.poi.poifs.eventfilesystem.POIFSReaderListener; + +/** + * Office file format Metadata Extracter + * + * @author Jesper Steen Møller + */ +public class OfficeMetadataExtracter extends AbstractMetadataExtracter +{ + public static String[] SUPPORTED_MIMETYPES = new String[] { + MimetypeMap.MIMETYPE_WORD, + MimetypeMap.MIMETYPE_EXCEL, + MimetypeMap.MIMETYPE_PPT}; + + public OfficeMetadataExtracter() + { + super(new HashSet(Arrays.asList(SUPPORTED_MIMETYPES)), 1.0, 1000); + } + + public void extractInternal(ContentReader reader, final Map destination) throws Throwable + { + POIFSReaderListener readerListener = new POIFSReaderListener() + { + public void processPOIFSReaderEvent(final POIFSReaderEvent event) + { + try + { + PropertySet ps = PropertySetFactory.create(event.getStream()); + if (ps instanceof SummaryInformation) + { + SummaryInformation si = (SummaryInformation) ps; + + // Titled aspect + trimPut(ContentModel.PROP_TITLE, si.getTitle(), destination); + trimPut(ContentModel.PROP_DESCRIPTION, si.getSubject(), destination); + + // Auditable aspect + trimPut(ContentModel.PROP_CREATED, si.getCreateDateTime(), destination); + trimPut(ContentModel.PROP_MODIFIED, si.getLastSaveDateTime(), destination); + trimPut(ContentModel.PROP_AUTHOR, si.getAuthor(), destination); + } + } + catch (Exception ex) + { + throw new ContentIOException("Property set stream: " + event.getPath() + event.getName(), ex); + } + } + }; + + InputStream is = null; + try + { + is = reader.getContentInputStream(); + POIFSReader poiFSReader = new POIFSReader(); + poiFSReader.registerListener(readerListener, SummaryInformation.DEFAULT_STREAM_NAME); + poiFSReader.read(is); + } + finally + { + if (is != null) + { + try { is.close(); } catch (IOException e) {} + } + } + } +} diff --git a/source/java/org/alfresco/repo/content/metadata/OpenOfficeMetadataExtracter.java b/source/java/org/alfresco/repo/content/metadata/OpenOfficeMetadataExtracter.java index 389b5a46bf..3511b627f8 100644 --- a/source/java/org/alfresco/repo/content/metadata/OpenOfficeMetadataExtracter.java +++ b/source/java/org/alfresco/repo/content/metadata/OpenOfficeMetadataExtracter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005 Jesper Steen M�ller + * Copyright (C) 2005 Jesper Steen Møller * * Licensed under the Mozilla Public License version 1.1 * with a permitted attribution clause. You may obtain a diff --git a/source/java/org/alfresco/repo/content/metadata/OpenOfficeMetadataExtracterTest.java b/source/java/org/alfresco/repo/content/metadata/OpenOfficeMetadataExtracterTest.java index 9648c52bba..cb25709f77 100644 --- a/source/java/org/alfresco/repo/content/metadata/OpenOfficeMetadataExtracterTest.java +++ b/source/java/org/alfresco/repo/content/metadata/OpenOfficeMetadataExtracterTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005 Jesper Steen M�ller + * Copyright (C) 2005 Jesper Steen Møller * * Licensed under the Mozilla Public License version 1.1 * with a permitted attribution clause. You may obtain a diff --git a/source/java/org/alfresco/repo/content/metadata/PdfBoxMetadataExtracter.java b/source/java/org/alfresco/repo/content/metadata/PdfBoxMetadataExtracter.java index 5f0d796058..d3296920de 100644 --- a/source/java/org/alfresco/repo/content/metadata/PdfBoxMetadataExtracter.java +++ b/source/java/org/alfresco/repo/content/metadata/PdfBoxMetadataExtracter.java @@ -1,75 +1,75 @@ -/* - * Copyright (C) 2005 Jesper Steen M�ller - * - * Licensed under the Mozilla Public License version 1.1 - * with a permitted attribution clause. You may obtain a - * copy of the License at - * - * http://www.alfresco.org/legal/license.txt - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - * either express or implied. See the License for the specific - * language governing permissions and limitations under the - * License. - */ -package org.alfresco.repo.content.metadata; - -import java.io.IOException; -import java.io.InputStream; -import java.io.Serializable; -import java.util.Calendar; -import java.util.Map; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.content.MimetypeMap; -import org.alfresco.service.cmr.repository.ContentReader; -import org.alfresco.service.namespace.QName; -import org.pdfbox.pdmodel.PDDocument; -import org.pdfbox.pdmodel.PDDocumentInformation; - -/** - * - * @author Jesper Steen Møller - */ -public class PdfBoxMetadataExtracter extends AbstractMetadataExtracter -{ - public PdfBoxMetadataExtracter() - { - super(MimetypeMap.MIMETYPE_PDF, 1.0, 1000); - } - - public void extractInternal(ContentReader reader, Map destination) throws Throwable - { - PDDocument pdf = null; - InputStream is = null; - try - { - is = reader.getContentInputStream(); - // stream the document in - pdf = PDDocument.load(is); - // Scoop out the metadata - PDDocumentInformation docInfo = pdf.getDocumentInformation(); - - trimPut(ContentModel.PROP_AUTHOR, docInfo.getAuthor(), destination); - trimPut(ContentModel.PROP_TITLE, docInfo.getTitle(), destination); - trimPut(ContentModel.PROP_DESCRIPTION, docInfo.getSubject(), destination); - - Calendar created = docInfo.getCreationDate(); - if (created != null) - destination.put(ContentModel.PROP_CREATED, created.getTime()); - } - finally - { - if (is != null) - { - try { is.close(); } catch (IOException e) {} - } - if (pdf != null) - { - try { pdf.close(); } catch (Throwable e) { e.printStackTrace(); } - } - } - } -} +/* + * Copyright (C) 2005 Jesper Steen Møller + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.content.metadata; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Serializable; +import java.util.Calendar; +import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.namespace.QName; +import org.pdfbox.pdmodel.PDDocument; +import org.pdfbox.pdmodel.PDDocumentInformation; + +/** + * + * @author Jesper Steen Møller + */ +public class PdfBoxMetadataExtracter extends AbstractMetadataExtracter +{ + public PdfBoxMetadataExtracter() + { + super(MimetypeMap.MIMETYPE_PDF, 1.0, 1000); + } + + public void extractInternal(ContentReader reader, Map destination) throws Throwable + { + PDDocument pdf = null; + InputStream is = null; + try + { + is = reader.getContentInputStream(); + // stream the document in + pdf = PDDocument.load(is); + // Scoop out the metadata + PDDocumentInformation docInfo = pdf.getDocumentInformation(); + + trimPut(ContentModel.PROP_AUTHOR, docInfo.getAuthor(), destination); + trimPut(ContentModel.PROP_TITLE, docInfo.getTitle(), destination); + trimPut(ContentModel.PROP_DESCRIPTION, docInfo.getSubject(), destination); + + Calendar created = docInfo.getCreationDate(); + if (created != null) + destination.put(ContentModel.PROP_CREATED, created.getTime()); + } + finally + { + if (is != null) + { + try { is.close(); } catch (IOException e) {} + } + if (pdf != null) + { + try { pdf.close(); } catch (Throwable e) { e.printStackTrace(); } + } + } + } +} diff --git a/source/java/org/alfresco/repo/content/metadata/PdfBoxMetadataExtracterTest.java b/source/java/org/alfresco/repo/content/metadata/PdfBoxMetadataExtracterTest.java index 70049a7e92..83cd43f7a5 100644 --- a/source/java/org/alfresco/repo/content/metadata/PdfBoxMetadataExtracterTest.java +++ b/source/java/org/alfresco/repo/content/metadata/PdfBoxMetadataExtracterTest.java @@ -1,43 +1,43 @@ -package org.alfresco.repo.content.metadata; - -import org.alfresco.repo.content.MimetypeMap; - -/** - * @see org.alfresco.repo.content.metadata.PdfBoxMetadataExtracter - * - * @author Jesper Steen Møller - */ -public class PdfBoxMetadataExtracterTest extends AbstractMetadataExtracterTest -{ - private MetadataExtracter extracter; - - @Override - public void setUp() throws Exception - { - super.setUp(); - extracter = new PdfBoxMetadataExtracter(); - } - - /** - * @return Returns the same transformer regardless - it is allowed - */ - protected MetadataExtracter getExtracter() - { - return extracter; - } - - public void testReliability() throws Exception - { - double reliability = 0.0; - reliability = extracter.getReliability(MimetypeMap.MIMETYPE_TEXT_PLAIN); - assertEquals("Mimetype should not be supported", 0.0, reliability); - - reliability = extracter.getReliability(MimetypeMap.MIMETYPE_PDF); - assertEquals("Mimetype should be supported", 1.0, reliability); - } - - public void testPdfExtraction() throws Exception - { - testExtractFromMimetype(MimetypeMap.MIMETYPE_PDF); - } -} +package org.alfresco.repo.content.metadata; + +import org.alfresco.repo.content.MimetypeMap; + +/** + * @see org.alfresco.repo.content.metadata.PdfBoxMetadataExtracter + * + * @author Jesper Steen Møller + */ +public class PdfBoxMetadataExtracterTest extends AbstractMetadataExtracterTest +{ + private MetadataExtracter extracter; + + @Override + public void setUp() throws Exception + { + super.setUp(); + extracter = new PdfBoxMetadataExtracter(); + } + + /** + * @return Returns the same transformer regardless - it is allowed + */ + protected MetadataExtracter getExtracter() + { + return extracter; + } + + public void testReliability() throws Exception + { + double reliability = 0.0; + reliability = extracter.getReliability(MimetypeMap.MIMETYPE_TEXT_PLAIN); + assertEquals("Mimetype should not be supported", 0.0, reliability); + + reliability = extracter.getReliability(MimetypeMap.MIMETYPE_PDF); + assertEquals("Mimetype should be supported", 1.0, reliability); + } + + public void testPdfExtraction() throws Exception + { + testExtractFromMimetype(MimetypeMap.MIMETYPE_PDF); + } +} diff --git a/source/java/org/alfresco/repo/content/transform/AbstractContentTransformer.java b/source/java/org/alfresco/repo/content/transform/AbstractContentTransformer.java index ee82eac135..8369e61f94 100644 --- a/source/java/org/alfresco/repo/content/transform/AbstractContentTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/AbstractContentTransformer.java @@ -252,13 +252,13 @@ public abstract class AbstractContentTransformer implements ContentTransformer finally { // check that the reader and writer are both closed - if (!reader.isClosed()) + if (reader.isChannelOpen()) { logger.error("Content reader not closed by transformer: \n" + " reader: " + reader + "\n" + " transformer: " + this); } - if (!writer.isClosed()) + if (writer.isChannelOpen()) { logger.error("Content writer not closed by transformer: \n" + " writer: " + writer + "\n" + diff --git a/source/java/org/alfresco/repo/content/transform/ContentTransformerRegistry.java b/source/java/org/alfresco/repo/content/transform/ContentTransformerRegistry.java index 242499ffc4..8f370dbc47 100644 --- a/source/java/org/alfresco/repo/content/transform/ContentTransformerRegistry.java +++ b/source/java/org/alfresco/repo/content/transform/ContentTransformerRegistry.java @@ -25,11 +25,8 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.repo.content.MimetypeMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.util.Assert; /** * Holds and provides the most appropriate content transformer for @@ -47,7 +44,6 @@ public class ContentTransformerRegistry private static final Log logger = LogFactory.getLog(ContentTransformerRegistry.class); private List transformers; - private MimetypeMap mimetypeMap; /** Cache of previously used transactions */ private Map> transformationCache; /** Controls read access to the transformation cache */ @@ -58,11 +54,8 @@ public class ContentTransformerRegistry /** * @param mimetypeMap all the mimetypes available to the system */ - public ContentTransformerRegistry(MimetypeMap mimetypeMap) + public ContentTransformerRegistry() { - Assert.notNull(mimetypeMap, "The MimetypeMap is mandatory"); - this.mimetypeMap = mimetypeMap; - this.transformers = new ArrayList(10); transformationCache = new HashMap>(17); @@ -143,16 +136,6 @@ public class ContentTransformerRegistry */ public ContentTransformer getTransformer(String sourceMimetype, String targetMimetype) { - // check that the mimetypes are valid - if (!mimetypeMap.getMimetypes().contains(sourceMimetype)) - { - throw new AlfrescoRuntimeException("Unknown source mimetype: " + sourceMimetype); - } - if (!mimetypeMap.getMimetypes().contains(targetMimetype)) - { - throw new AlfrescoRuntimeException("Unknown target mimetype: " + targetMimetype); - } - TransformationKey key = new TransformationKey(sourceMimetype, targetMimetype); List transformers = null; transformationCacheReadLock.lock(); diff --git a/source/java/org/alfresco/repo/content/transform/ContentTransformerRegistryTest.java b/source/java/org/alfresco/repo/content/transform/ContentTransformerRegistryTest.java index 0b0ac21f98..dac9c68c14 100644 --- a/source/java/org/alfresco/repo/content/transform/ContentTransformerRegistryTest.java +++ b/source/java/org/alfresco/repo/content/transform/ContentTransformerRegistryTest.java @@ -69,7 +69,7 @@ public class ContentTransformerRegistryTest extends AbstractContentTransformerTe bytes[i] = (byte)i; } // create the dummyRegistry - dummyRegistry = new ContentTransformerRegistry(mimetypeMap); + dummyRegistry = new ContentTransformerRegistry(); // create some dummy transformers for reliability tests new DummyTransformer(mimetypeMap, dummyRegistry, A, B, 0.3, 10L); new DummyTransformer(mimetypeMap, dummyRegistry, A, B, 0.6, 10L); diff --git a/source/java/org/alfresco/repo/content/transform/HtmlParserContentTransformerTest.java b/source/java/org/alfresco/repo/content/transform/HtmlParserContentTransformerTest.java index 779ad2dd2d..8ae227fd17 100644 --- a/source/java/org/alfresco/repo/content/transform/HtmlParserContentTransformerTest.java +++ b/source/java/org/alfresco/repo/content/transform/HtmlParserContentTransformerTest.java @@ -25,8 +25,6 @@ import org.alfresco.repo.content.MimetypeMap; */ public class HtmlParserContentTransformerTest extends AbstractContentTransformerTest { - private static final String SOME_CONTENT = "azAz10!£$%^&*()\t\r\n"; - private ContentTransformer transformer; @Override diff --git a/source/java/org/alfresco/repo/copy/CopyServiceImpl.java b/source/java/org/alfresco/repo/copy/CopyServiceImpl.java index 352c58cd26..f0e1b7be09 100644 --- a/source/java/org/alfresco/repo/copy/CopyServiceImpl.java +++ b/source/java/org/alfresco/repo/copy/CopyServiceImpl.java @@ -444,26 +444,29 @@ public class CopyServiceImpl implements CopyService * @param destinationNodeRef the destination node reference */ private void copyPermissions(NodeRef sourceNodeRef, NodeRef destinationNodeRef) - { - // Get the permission details of the source node reference - Set permissions = this.permissionService.getAllSetPermissions(sourceNodeRef); - boolean includeInherited = this.permissionService.getInheritParentPermissions(sourceNodeRef); - - AccessStatus writePermission = permissionService.hasPermission(destinationNodeRef, PermissionService.CHANGE_PERMISSIONS); - if (this.authenticationService.isCurrentUserTheSystemUser() || writePermission.equals(AccessStatus.ALLOWED)) + { + if(this.permissionService.hasPermission(sourceNodeRef, PermissionService.READ_PERMISSIONS) == AccessStatus.ALLOWED) { - // Set the permission values on the destination node - for (AccessPermission permission : permissions) - { - this.permissionService.setPermission( - destinationNodeRef, - permission.getAuthority(), - permission.getPermission(), - permission.getAccessStatus().equals(AccessStatus.ALLOWED)); - } - this.permissionService.setInheritParentPermissions(destinationNodeRef, includeInherited); + // Get the permission details of the source node reference + Set permissions = this.permissionService.getAllSetPermissions(sourceNodeRef); + boolean includeInherited = this.permissionService.getInheritParentPermissions(sourceNodeRef); + + AccessStatus writePermission = permissionService.hasPermission(destinationNodeRef, PermissionService.CHANGE_PERMISSIONS); + if (writePermission.equals(AccessStatus.ALLOWED) || this.authenticationService.isCurrentUserTheSystemUser() ) + { + // Set the permission values on the destination node + for (AccessPermission permission : permissions) + { + this.permissionService.setPermission( + destinationNodeRef, + permission.getAuthority(), + permission.getPermission(), + permission.getAccessStatus().equals(AccessStatus.ALLOWED)); + } + this.permissionService.setInheritParentPermissions(destinationNodeRef, includeInherited); + } } - } + } /** * Gets the copy details. This calls the appropriate policies that have been registered @@ -536,7 +539,13 @@ public class CopyServiceImpl implements CopyService ClassDefinition classDefinition = this.dictionaryService.getClass(classRef); if (classDefinition != null) { - // Copy the properties + if (classDefinition.isAspect() == true) + { + // make sure any aspects without any properties or associations are copied + copyDetails.addAspect(classRef); + } + + // Copy the properties Map propertyDefinitions = classDefinition.getProperties(); for (QName propertyName : propertyDefinitions.keySet()) { diff --git a/source/java/org/alfresco/repo/copy/CopyServiceImplTest.java b/source/java/org/alfresco/repo/copy/CopyServiceImplTest.java index 93b42a7d8e..973ff0c827 100644 --- a/source/java/org/alfresco/repo/copy/CopyServiceImplTest.java +++ b/source/java/org/alfresco/repo/copy/CopyServiceImplTest.java @@ -376,17 +376,20 @@ public class CopyServiceImplTest extends BaseSpringTest public void testCopyNodeWithRules() { // Create a new rule and add it to the source noderef - Rule rule = this.ruleService.createRule(RuleType.INBOUND); + Rule rule = new Rule(); + rule.setRuleType(RuleType.INBOUND); Map props = new HashMap(1); props.put(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_VERSIONABLE); Action action = this.actionService.createAction(AddFeaturesActionExecuter.NAME, props); - rule.addAction(action); + rule.setAction(action); ActionCondition actionCondition = this.actionService.createActionCondition(NoConditionEvaluator.NAME); - rule.addActionCondition(actionCondition); + action.addActionCondition(actionCondition); this.ruleService.saveRule(this.sourceNodeRef, rule); + assertNotNull(rule.getNodeRef()); + assertEquals(this.sourceNodeRef, this.ruleService.getOwningNodeRef(rule)); //System.out.println( // NodeStoreInspector.dumpNodeStore(this.nodeService, this.storeRef)); @@ -403,20 +406,22 @@ public class CopyServiceImplTest extends BaseSpringTest //System.out.println( // NodeStoreInspector.dumpNodeStore(this.nodeService, this.storeRef)); - checkCopiedNode(this.sourceNodeRef, copy, true, true, true); - - //assertTrue(this.configurableService.isConfigurable(copy)); - //assertNotNull(this.configurableService.getConfigurationFolder(copy)); - //assertFalse(this.configurableService.getConfigurationFolder(this.sourceNodeRef) == this.configurableService.getConfigurationFolder(copy)); + checkCopiedNode(this.sourceNodeRef, copy, true, true, true); assertTrue(this.nodeService.hasAspect(copy, RuleModel.ASPECT_RULES)); assertTrue(this.ruleService.hasRules(copy)); assertTrue(this.ruleService.rulesEnabled(copy)); + List copiedRules = this.ruleService.getRules(copy); assertEquals(1, copiedRules.size()); Rule copiedRule = copiedRules.get(0); - assertFalse(rule.getId() == copiedRule.getId()); - assertEquals(rule.getAction(0).getActionDefinitionName(), copiedRule.getAction(0).getActionDefinitionName()); + + assertNotNull(copiedRule.getNodeRef()); + assertFalse(copiedRule.getNodeRef().equals(rule.getNodeRef())); + assertEquals(rule.getTitle(), copiedRule.getTitle()); + assertEquals(rule.getDescription(), copiedRule.getDescription()); + assertEquals(copy, this.ruleService.getOwningNodeRef(copiedRule)); + assertEquals(rule.getAction().getActionDefinitionName(), copiedRule.getAction().getActionDefinitionName()); // Now copy the node without copying the children and check that the rules have been copied NodeRef copy2 = this.copyService.copy( @@ -441,8 +446,11 @@ public class CopyServiceImplTest extends BaseSpringTest List copiedRules2 = this.ruleService.getRules(copy2); assertEquals(1, copiedRules.size()); Rule copiedRule2 = copiedRules2.get(0); - assertFalse(rule.getId() == copiedRule2.getId()); - assertEquals(rule.getAction(0).getActionDefinitionName(), copiedRule2.getAction(0).getActionDefinitionName()); + assertFalse(rule.getNodeRef().equals(copiedRule2.getNodeRef())); + assertEquals(rule.getTitle(), copiedRule2.getTitle()); + assertEquals(rule.getDescription(), copiedRule2.getDescription()); + assertEquals(this.ruleService.getOwningNodeRef(copiedRule2), copy2); + assertEquals(rule.getAction().getActionDefinitionName(), copiedRule2.getAction().getActionDefinitionName()); } public void testCopyToExistingNode() @@ -531,11 +539,12 @@ public class CopyServiceImplTest extends BaseSpringTest params.put(MoveActionExecuter.PARAM_DESTINATION_FOLDER, nodeTwo); params.put(MoveActionExecuter.PARAM_ASSOC_TYPE_QNAME, TEST_CHILD_ASSOC_TYPE_QNAME); params.put(MoveActionExecuter.PARAM_ASSOC_QNAME, QName.createQName("{test}ruleCopy")); - Rule rule = this.ruleService.createRule(RuleType.INBOUND); - ActionCondition condition = this.actionService.createActionCondition(NoConditionEvaluator.NAME); - rule.addActionCondition(condition); + Rule rule = new Rule(); + rule.setRuleType(RuleType.INBOUND); Action action = this.actionService.createAction(CopyActionExecuter.NAME, params); - rule.addAction(action); + ActionCondition condition = this.actionService.createActionCondition(NoConditionEvaluator.NAME); + action.addActionCondition(condition); + rule.setAction(action); this.ruleService.saveRule(nodeOne, rule); // Do a deep copy @@ -609,10 +618,8 @@ public class CopyServiceImplTest extends BaseSpringTest assertEquals(1, rules.size()); Rule copiedRule = rules.get(0); assertNotNull(copiedRule); - List ruleActions = copiedRule.getActions(); - assertNotNull(ruleActions); - assertEquals(1, ruleActions.size()); - Action ruleAction = ruleActions.get(0); + Action ruleAction = copiedRule.getAction(); + assertNotNull(ruleAction); NodeRef value = (NodeRef)ruleAction.getParameterValue(MoveActionExecuter.PARAM_DESTINATION_FOLDER); assertNotNull(value); assertEquals(nodeTwoCopy, value); diff --git a/source/java/org/alfresco/repo/descriptor/DescriptorServiceImpl.java b/source/java/org/alfresco/repo/descriptor/DescriptorServiceImpl.java index 92aa77577b..158b3fae5b 100644 --- a/source/java/org/alfresco/repo/descriptor/DescriptorServiceImpl.java +++ b/source/java/org/alfresco/repo/descriptor/DescriptorServiceImpl.java @@ -44,6 +44,7 @@ import org.alfresco.service.transaction.TransactionService; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeansException; +import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; @@ -58,7 +59,7 @@ import org.springframework.core.io.Resource; * * @author David Caruana */ -public class DescriptorServiceImpl implements DescriptorService, ApplicationListener, InitializingBean, ApplicationContextAware +public class DescriptorServiceImpl implements DescriptorService, ApplicationListener, InitializingBean, ApplicationContextAware, DisposableBean { private static Log logger = LogFactory.getLog(DescriptorServiceImpl.class); @@ -201,6 +202,13 @@ public class DescriptorServiceImpl implements DescriptorService, ApplicationList serverDescriptor = createServerDescriptor(); } + /** + * Destruction hook + */ + public void destroy() throws Exception + { + } + /** * Create server descriptor * @@ -262,6 +270,7 @@ public class DescriptorServiceImpl implements DescriptorService, ApplicationList nodeService.setProperty(currentDescriptorNodeRef, ContentModel.PROP_SYS_VERSION_MINOR, serverDescriptor.getVersionMinor()); nodeService.setProperty(currentDescriptorNodeRef, ContentModel.PROP_SYS_VERSION_REVISION, serverDescriptor.getVersionRevision()); nodeService.setProperty(currentDescriptorNodeRef, ContentModel.PROP_SYS_VERSION_LABEL, serverDescriptor.getVersionLabel()); + nodeService.setProperty(currentDescriptorNodeRef, ContentModel.PROP_SYS_VERSION_BUILD, serverDescriptor.getVersionBuild()); nodeService.setProperty(currentDescriptorNodeRef, ContentModel.PROP_SYS_VERSION_SCHEMA, serverDescriptor.getSchema()); // done @@ -441,6 +450,14 @@ public class DescriptorServiceImpl implements DescriptorService, ApplicationList return "Unknown"; } + /* (non-Javadoc) + * @see org.alfresco.service.descriptor.Descriptor#getVersionBuild() + */ + public String getVersionBuild() + { + return "Unknown"; + } + /* (non-Javadoc) * @see org.alfresco.service.descriptor.Descriptor#getVersion() */ @@ -483,14 +500,99 @@ public class DescriptorServiceImpl implements DescriptorService, ApplicationList } } + /** + * Base class for Descriptor implementations, provides a + * default getVersion() implementation. + * + * @author gavinc + */ + public abstract class BaseDescriptor implements Descriptor + { + /* (non-Javadoc) + * @see org.alfresco.service.descriptor.Descriptor#getVersion() + */ + public String getVersion() + { + StringBuilder version = new StringBuilder(getVersionMajor()); + version.append("."); + version.append(getVersionMinor()); + version.append("."); + version.append(getVersionRevision()); + + String label = getVersionLabel(); + String build = getVersionBuild(); + + boolean hasLabel = (label != null && label.length() > 0); + boolean hasBuild = (build != null && build.length() > 0); + + // add opening bracket if either a label or build number is present + if (hasLabel || hasBuild) + { + version.append(" ("); + } + + // add label if present + if (hasLabel) + { + version.append(label); + } + + // add build number is present + if (hasBuild) + { + // if there is also a label we need a separating space + if (hasLabel) + { + version.append(" "); + } + + version.append(build); + } + + // add closing bracket if either a label or build number is present + if (hasLabel || hasBuild) + { + version.append(")"); + } + + return version.toString(); + } + + /** + * Returns the int representation of the given schema string + * + * @param schemaStr The schema number as a string + * @return The schema number as an int + */ + protected int getSchema(String schemaStr) + { + if (schemaStr == null) + { + return 0; + } + try + { + int schema = Integer.parseInt(schemaStr); + if (schema < 0) + { + throw new NumberFormatException(); + } + return schema; + } + catch (NumberFormatException e) + { + throw new AlfrescoRuntimeException("Schema must be a positive integer '" + schemaStr + "' is not!"); + } + } + } + /** * Repository Descriptor whose meta-data is retrieved from the repository store */ - private class RepositoryDescriptor implements Descriptor + private class RepositoryDescriptor extends BaseDescriptor { private Map properties; - /** * Construct * @@ -534,17 +636,11 @@ public class DescriptorServiceImpl implements DescriptorService, ApplicationList } /* (non-Javadoc) - * @see org.alfresco.service.descriptor.Descriptor#getVersion() + * @see org.alfresco.service.descriptor.Descriptor#getVersionBuild() */ - public String getVersion() + public String getVersionBuild() { - String version = getVersionMajor() + "." + getVersionMinor() + "." + getVersionRevision(); - String label = getVersionLabel(); - if (label != null && label.length() > 0) - { - version += " (" + label + ")"; - } - return version; + return getDescriptor("sys:versionBuild"); } /* (non-Javadoc) @@ -561,24 +657,7 @@ public class DescriptorServiceImpl implements DescriptorService, ApplicationList */ public int getSchema() { - String schemaStr = getDescriptor("sys:versionSchema"); - if (schemaStr == null) - { - return 0; - } - try - { - int schema = Integer.parseInt(schemaStr); - if (schema < 0) - { - throw new NumberFormatException(); - } - return schema; - } - catch (NumberFormatException e) - { - throw new AlfrescoRuntimeException("'version.schema' must be a positive integer"); - } + return getSchema(getDescriptor("sys:versionSchema")); } /* (non-Javadoc) @@ -610,7 +689,7 @@ public class DescriptorServiceImpl implements DescriptorService, ApplicationList /** * Server Descriptor whose meta-data is retrieved from run-time environment */ - private class ServerDescriptor implements Descriptor + private class ServerDescriptor extends BaseDescriptor { /* (non-Javadoc) * @see org.alfresco.service.descriptor.Descriptor#getVersionMajor() @@ -643,19 +722,13 @@ public class DescriptorServiceImpl implements DescriptorService, ApplicationList { return serverProperties.getProperty("version.label"); } - + /* (non-Javadoc) - * @see org.alfresco.service.descriptor.Descriptor#getVersion() + * @see org.alfresco.service.descriptor.Descriptor#getVersionBuild() */ - public String getVersion() + public String getVersionBuild() { - String version = getVersionMajor() + "." + getVersionMinor() + "." + getVersionRevision(); - String label = getVersionLabel(); - if (label != null && label.length() > 0) - { - version += " (" + label + ")"; - } - return version; + return serverProperties.getProperty("version.build"); } /* (non-Javadoc) @@ -672,24 +745,7 @@ public class DescriptorServiceImpl implements DescriptorService, ApplicationList */ public int getSchema() { - String schemaStr = serverProperties.getProperty("version.schema"); - if (schemaStr == null) - { - return 0; - } - try - { - int schema = Integer.parseInt(schemaStr); - if (schema < 0) - { - throw new NumberFormatException(); - } - return schema; - } - catch (NumberFormatException e) - { - throw new AlfrescoRuntimeException("'version.schema' must be a positive integer"); - } + return getSchema(serverProperties.getProperty("version.schema")); } /* (non-Javadoc) @@ -710,5 +766,4 @@ public class DescriptorServiceImpl implements DescriptorService, ApplicationList return serverProperties.getProperty(key, ""); } } - } diff --git a/source/java/org/alfresco/repo/descriptor/DescriptorServiceTest.java b/source/java/org/alfresco/repo/descriptor/DescriptorServiceTest.java index 06809133b0..fae4669246 100644 --- a/source/java/org/alfresco/repo/descriptor/DescriptorServiceTest.java +++ b/source/java/org/alfresco/repo/descriptor/DescriptorServiceTest.java @@ -72,11 +72,9 @@ public class DescriptorServiceTest extends BaseSpringTest String minor = serverDescriptor.getVersionMinor(); String revision = serverDescriptor.getVersionRevision(); String label = serverDescriptor.getVersionLabel(); + String build = serverDescriptor.getVersionBuild(); String version = major + "." + minor + "." + revision; - if (label != null && label.length() > 0) - { - version += " (" + label + ")"; - } + version = buildVersionString(version, label, build); assertEquals(version, serverDescriptor.getVersion()); @@ -94,11 +92,9 @@ public class DescriptorServiceTest extends BaseSpringTest String minor = repoDescriptor.getVersionMinor(); String revision = repoDescriptor.getVersionRevision(); String label = repoDescriptor.getVersionLabel(); + String build = repoDescriptor.getVersionBuild(); String version = major + "." + minor + "." + revision; - if (label != null && label.length() > 0) - { - version += " (" + label + ")"; - } + version = buildVersionString(version, label, build); assertEquals(version, repoDescriptor.getVersion()); @@ -106,4 +102,43 @@ public class DescriptorServiceTest extends BaseSpringTest assertTrue("Repository schema version must be greater than -1", schemaVersion > -1); } + private String buildVersionString(String version, String label, String build) + { + StringBuilder builder = new StringBuilder(version); + + boolean hasLabel = (label != null && label.length() > 0); + boolean hasBuild = (build != null && build.length() > 0); + + // add opening bracket if either a label or build number is present + if (hasLabel || hasBuild) + { + builder.append(" ("); + } + + // add label if present + if (hasLabel) + { + builder.append(label); + } + + // add build number is present + if (hasBuild) + { + // if there is also a label we need a separating space + if (hasLabel) + { + builder.append(" "); + } + + builder.append(build); + } + + // add closing bracket if either a label or build number is present + if (hasLabel || hasBuild) + { + builder.append(")"); + } + + return builder.toString(); + } } diff --git a/source/java/org/alfresco/repo/dictionary/DictionaryComponent.java b/source/java/org/alfresco/repo/dictionary/DictionaryComponent.java index 7c7493e609..2b9722b49e 100644 --- a/source/java/org/alfresco/repo/dictionary/DictionaryComponent.java +++ b/source/java/org/alfresco/repo/dictionary/DictionaryComponent.java @@ -107,7 +107,7 @@ public class DictionaryComponent implements DictionaryService */ public Collection getAllTypes() { - Collection types = new ArrayList(); + Collection types = new ArrayList(100); for (QName model : getAllModels()) { types.addAll(getTypes(model)); @@ -136,7 +136,7 @@ public class DictionaryComponent implements DictionaryService */ public Collection getAllAspects() { - Collection aspects = new ArrayList(); + Collection aspects = new ArrayList(64); for (QName model : getAllModels()) { aspects.addAll(getAspects(model)); diff --git a/source/java/org/alfresco/repo/dictionary/DictionaryDAOImpl.java b/source/java/org/alfresco/repo/dictionary/DictionaryDAOImpl.java index efc473dc4d..50306137e4 100644 --- a/source/java/org/alfresco/repo/dictionary/DictionaryDAOImpl.java +++ b/source/java/org/alfresco/repo/dictionary/DictionaryDAOImpl.java @@ -18,7 +18,9 @@ package org.alfresco.repo.dictionary; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.alfresco.service.cmr.dictionary.AspectDefinition; @@ -52,8 +54,8 @@ public class DictionaryDAOImpl implements DictionaryDAO // Namespace Data Access private NamespaceDAO namespaceDAO; - // Map of namespace to model name - private Map namespaceToModel = new HashMap(); + // Map of Namespace URI usages to Models + private Map> uriToModels = new HashMap>(); // Map of model name to compiled model private Map compiledModels = new HashMap(); @@ -90,7 +92,11 @@ public class DictionaryDAOImpl implements DictionaryDAO { namespaceDAO.removePrefix(namespace.getPrefix()); namespaceDAO.removeURI(namespace.getUri()); - namespaceToModel.remove(namespace.getUri()); + unmapUriToModel(namespace.getUri(), previousVersion); + } + for (M2Namespace importNamespace : previousVersion.getM2Model().getImports()) + { + unmapUriToModel(importNamespace.getUri(), previousVersion); } } @@ -99,7 +105,11 @@ public class DictionaryDAOImpl implements DictionaryDAO { namespaceDAO.addURI(namespace.getUri()); namespaceDAO.addPrefix(namespace.getPrefix(), namespace.getUri()); - namespaceToModel.put(namespace.getUri(), modelName); + mapUriToModel(namespace.getUri(), compiledModel); + } + for (M2Namespace importNamespace : model.getImports()) + { + mapUriToModel(importNamespace.getUri(), compiledModel); } // Publish new Model Definition @@ -115,6 +125,7 @@ public class DictionaryDAOImpl implements DictionaryDAO } } + /** * @see org.alfresco.repo.dictionary.DictionaryDAO#removeModel(org.alfresco.service.namespace.QName) */ @@ -129,7 +140,7 @@ public class DictionaryDAOImpl implements DictionaryDAO { namespaceDAO.removePrefix(namespace.getPrefix()); namespaceDAO.removeURI(namespace.getUri()); - namespaceToModel.remove(namespace.getUri()); + unmapUriToModel(namespace.getUri(), compiledModel); } // Remove the model from the list @@ -137,18 +148,61 @@ public class DictionaryDAOImpl implements DictionaryDAO } } + + /** + * Map Namespace URI to Model + * + * @param uri namespace uri + * @param model model + */ + private void mapUriToModel(String uri, CompiledModel model) + { + List models = uriToModels.get(uri); + if (models == null) + { + models = new ArrayList(); + uriToModels.put(uri, models); + } + if (!models.contains(model)) + { + models.add(model); + } + } + /** - * @param uri the namespace uri - * @return the compiled model which defines the specified namespace + * Unmap Namespace URI from Model + * + * @param uri namespace uri + * @param model model */ - private CompiledModel getCompiledModelForNamespace(String uri) + private void unmapUriToModel(String uri, CompiledModel model) { - QName modelName = namespaceToModel.get(uri); - return (modelName == null) ? null : getCompiledModel(modelName); + List models = uriToModels.get(uri); + if (models != null) + { + models.remove(model); + } + } + + + /** + * Get Models mapped to Namespace Uri + * + * @param uri namespace uri + * @return mapped models + */ + private List getModelsForUri(String uri) + { + List models = uriToModels.get(uri); + if (models == null) + { + models = Collections.emptyList(); + } + return models; } - + /** * @param modelName the model name * @return the compiled model of the given name @@ -170,8 +224,16 @@ public class DictionaryDAOImpl implements DictionaryDAO */ public DataTypeDefinition getDataType(QName typeName) { - CompiledModel model = getCompiledModelForNamespace(typeName.getNamespaceURI()); - return (model == null) ? null : model.getDataType(typeName); + List models = getModelsForUri(typeName.getNamespaceURI()); + for (CompiledModel model : models) + { + DataTypeDefinition dataType = model.getDataType(typeName); + if (dataType != null) + { + return dataType; + } + } + return null; } @@ -207,8 +269,16 @@ public class DictionaryDAOImpl implements DictionaryDAO */ public TypeDefinition getType(QName typeName) { - CompiledModel model = getCompiledModelForNamespace(typeName.getNamespaceURI()); - return (model == null) ? null : model.getType(typeName); + List models = getModelsForUri(typeName.getNamespaceURI()); + for (CompiledModel model : models) + { + TypeDefinition type = model.getType(typeName); + if (type != null) + { + return type; + } + } + return null; } @@ -217,8 +287,16 @@ public class DictionaryDAOImpl implements DictionaryDAO */ public AspectDefinition getAspect(QName aspectName) { - CompiledModel model = getCompiledModelForNamespace(aspectName.getNamespaceURI()); - return (model == null) ? null : model.getAspect(aspectName); + List models = getModelsForUri(aspectName.getNamespaceURI()); + for (CompiledModel model : models) + { + AspectDefinition aspect = model.getAspect(aspectName); + if (aspect != null) + { + return aspect; + } + } + return null; } @@ -227,8 +305,16 @@ public class DictionaryDAOImpl implements DictionaryDAO */ public ClassDefinition getClass(QName className) { - CompiledModel model = getCompiledModelForNamespace(className.getNamespaceURI()); - return (model == null) ? null : model.getClass(className); + List models = getModelsForUri(className.getNamespaceURI()); + for (CompiledModel model : models) + { + ClassDefinition classDef = model.getClass(className); + if (classDef != null) + { + return classDef; + } + } + return null; } @@ -237,23 +323,52 @@ public class DictionaryDAOImpl implements DictionaryDAO */ public PropertyDefinition getProperty(QName propertyName) { - CompiledModel model = getCompiledModelForNamespace(propertyName.getNamespaceURI()); - return (model == null) ? null : model.getProperty(propertyName); + List models = getModelsForUri(propertyName.getNamespaceURI()); + for (CompiledModel model : models) + { + PropertyDefinition propDef = model.getProperty(propertyName); + if (propDef != null) + { + return propDef; + } + } + return null; } + + /* (non-Javadoc) + * @see org.alfresco.repo.dictionary.ModelQuery#getConstraint(org.alfresco.service.namespace.QName) + */ public ConstraintDefinition getConstraint(QName constraintQName) { - CompiledModel model = getCompiledModelForNamespace(constraintQName.getNamespaceURI()); - return (model == null) ? null : model.getConstraint(constraintQName); + List models = getModelsForUri(constraintQName.getNamespaceURI()); + for (CompiledModel model : models) + { + ConstraintDefinition constraintDef = model.getConstraint(constraintQName); + if (constraintDef != null) + { + return constraintDef; + } + } + return null; } + /* (non-Javadoc) * @see org.alfresco.repo.dictionary.impl.ModelQuery#getAssociation(org.alfresco.repo.ref.QName) */ public AssociationDefinition getAssociation(QName assocName) { - CompiledModel model = getCompiledModelForNamespace(assocName.getNamespaceURI()); - return (model == null) ? null : model.getAssociation(assocName); + List models = getModelsForUri(assocName.getNamespaceURI()); + for (CompiledModel model : models) + { + AssociationDefinition assocDef = model.getAssociation(assocName); + if (assocDef != null) + { + return assocDef; + } + } + return null; } diff --git a/source/java/org/alfresco/repo/dictionary/DictionaryDAOTest.java b/source/java/org/alfresco/repo/dictionary/DictionaryDAOTest.java index bae285a5ea..171cdd54e8 100644 --- a/source/java/org/alfresco/repo/dictionary/DictionaryDAOTest.java +++ b/source/java/org/alfresco/repo/dictionary/DictionaryDAOTest.java @@ -186,6 +186,15 @@ public class DictionaryDAOTest extends TestCase ClassDefinition fileClassDef = service.getClass(testFileQName); assertTrue("File type should have the archive flag", fileClassDef.isArchive()); + QName testFileDerivedQName = QName.createQName(TEST_URL, "file-derived"); + ClassDefinition fileDerivedClassDef = service.getClass(testFileDerivedQName); + assertTrue("Direct derived File type should have the archive flag", fileDerivedClassDef.isArchive()); + + QName testFileDerivedNoArchiveQName = QName.createQName(TEST_URL, "file-derived-no-archive"); + ClassDefinition fileDerivedNoArchiveClassDef = service.getClass(testFileDerivedNoArchiveQName); + assertFalse("Derived File with archive override type should NOT have the archive flag", + fileDerivedNoArchiveClassDef.isArchive()); + QName testFolderQName = QName.createQName(TEST_URL, "folder"); ClassDefinition folderClassDef = service.getClass(testFolderQName); assertFalse("Folder type should not have the archive flag", folderClassDef.isArchive()); diff --git a/source/java/org/alfresco/repo/dictionary/M2Class.java b/source/java/org/alfresco/repo/dictionary/M2Class.java index 7cece04111..a08f4dd88c 100644 --- a/source/java/org/alfresco/repo/dictionary/M2Class.java +++ b/source/java/org/alfresco/repo/dictionary/M2Class.java @@ -32,7 +32,7 @@ public abstract class M2Class private String title = null; private String description = null; private String parentName = null; - private boolean archive = false; + private Boolean archive = null; private List properties = new ArrayList(); private List propertyOverrides = new ArrayList(); @@ -92,14 +92,14 @@ public abstract class M2Class } - public boolean isArchive() + public Boolean getArchive() { return archive; } public void setArchive(boolean archive) { - this.archive = archive; + this.archive = Boolean.valueOf(archive); } public M2Property createProperty(String name) diff --git a/source/java/org/alfresco/repo/dictionary/M2ClassDefinition.java b/source/java/org/alfresco/repo/dictionary/M2ClassDefinition.java index 7b759472de..b9f2600bab 100644 --- a/source/java/org/alfresco/repo/dictionary/M2ClassDefinition.java +++ b/source/java/org/alfresco/repo/dictionary/M2ClassDefinition.java @@ -46,7 +46,6 @@ import org.alfresco.service.namespace.QName; protected M2Class m2Class; protected QName name; protected QName parentName = null; - protected boolean archive = false; private Map propertyOverrides = new HashMap(); private Map properties = new HashMap(); @@ -57,8 +56,9 @@ import org.alfresco.service.namespace.QName; private List defaultAspects = new ArrayList(); private List defaultAspectNames = new ArrayList(); private List inheritedDefaultAspects = new ArrayList(); + private Boolean archive = null; + private Boolean inheritedArchive = null; - /** * Construct * @@ -74,7 +74,7 @@ import org.alfresco.service.namespace.QName; // Resolve Names this.name = QName.createQName(m2Class.getName(), resolver); - this.archive = m2Class.isArchive(); + this.archive = m2Class.getArchive(); if (m2Class.getParentName() != null && m2Class.getParentName().length() > 0) { this.parentName = QName.createQName(m2Class.getParentName(), resolver); @@ -162,8 +162,8 @@ import org.alfresco.service.namespace.QName; public String toString() { StringBuilder sb = new StringBuilder(120); - sb.append("ClassDef ") - .append("[ name=").append(name) + sb.append("ClassDef") + .append("[name=").append(name) .append("]"); return sb.toString(); } @@ -191,6 +191,15 @@ import org.alfresco.service.namespace.QName; { ((M2AssociationDefinition)def).resolveDependencies(query); } + + for (Map.Entry override : propertyOverrides.entrySet()) + { + PropertyDefinition propDef = query.getProperty(override.getKey()); + if (propDef == null) + { + throw new DictionaryException("Class " + name.toPrefixString() + " attempting to override property " + override.getKey().toPrefixString() + " which does not exist"); + } + } for (QName aspectName : defaultAspectNames) { @@ -280,6 +289,13 @@ import org.alfresco.service.namespace.QName; inheritedDefaultAspects.add(def); } } + + // resolve archive inheritance + if (parentClass != null && archive == null) + { + // archive not explicitly set on this class and there is a parent class + inheritedArchive = ((M2ClassDefinition)parentClass).isArchive(); + } } /* (non-Javadoc) @@ -340,8 +356,23 @@ import org.alfresco.service.namespace.QName; return (m2Class instanceof M2Aspect); } + /** + * @return Returns the archive flag, which defaults to false + */ public boolean isArchive() { + if (archive == null) + { + if (inheritedArchive != null) + { + return inheritedArchive.booleanValue(); + } + else + { + // default to false + return false; + } + } return archive; } diff --git a/source/java/org/alfresco/repo/dictionary/dictionarydaotest_model.xml b/source/java/org/alfresco/repo/dictionary/dictionarydaotest_model.xml index 32e074b425..fc8748f662 100644 --- a/source/java/org/alfresco/repo/dictionary/dictionarydaotest_model.xml +++ b/source/java/org/alfresco/repo/dictionary/dictionarydaotest_model.xml @@ -150,6 +150,15 @@ + + test:file + + + + test:file + false + + test:base diff --git a/source/java/org/alfresco/repo/domain/ChildAssoc.java b/source/java/org/alfresco/repo/domain/ChildAssoc.java index 72978ec4ea..6a3035670c 100644 --- a/source/java/org/alfresco/repo/domain/ChildAssoc.java +++ b/source/java/org/alfresco/repo/domain/ChildAssoc.java @@ -66,6 +66,28 @@ public interface ChildAssoc extends Comparable */ public void setTypeQName(QName assocTypeQName); + /** + * @return Returns the child node name. This may be truncated, in which case it + * will end with ... + */ + public String getChildNodeName(); + + /** + * @param childNodeName the name of the child node, which may be truncated and + * terminated with ... in order to not exceed 50 characters. + */ + public void setChildNodeName(String childNodeName); + + /** + * @return Returns the crc value for the child node name. + */ + public long getChildNodeNameCrc(); + + /** + * @param crc the crc value + */ + public void setChildNodeNameCrc(long crc); + /** * @return Returns the qualified name of this association */ diff --git a/source/java/org/alfresco/repo/domain/Node.java b/source/java/org/alfresco/repo/domain/Node.java index f988b5554d..da46574599 100644 --- a/source/java/org/alfresco/repo/domain/Node.java +++ b/source/java/org/alfresco/repo/domain/Node.java @@ -56,24 +56,8 @@ public interface Node public void setTypeQName(QName typeQName); -// public NodeStatus getStatus(); -// -// public void setStatus(NodeStatus status); -// public Set getAspects(); - /** - * @return Returns all the regular associations for which this node is a target - */ - public Collection getSourceNodeAssocs(); - - /** - * @return Returns all the regular associations for which this node is a source - */ - public Collection getTargetNodeAssocs(); - - public Collection getChildAssocs(); - public Collection getParentAssocs(); public Map getProperties(); diff --git a/source/java/org/alfresco/repo/domain/NodeAssoc.java b/source/java/org/alfresco/repo/domain/NodeAssoc.java index d4ccaca1f5..d2c93757b3 100644 --- a/source/java/org/alfresco/repo/domain/NodeAssoc.java +++ b/source/java/org/alfresco/repo/domain/NodeAssoc.java @@ -42,12 +42,6 @@ public interface NodeAssoc */ public void buildAssociation(Node sourceNode, Node targetNode); - /** - * Performs the necessary work on the {@link #getSource()() source} and - * {@link #getTarget()() target} nodes to maintain the inverse association sets - */ - public void removeAssociation(); - public AssociationRef getNodeAssocRef(); public Node getSource(); diff --git a/source/java/org/alfresco/repo/domain/hibernate/AppliedPatch.hbm.xml b/source/java/org/alfresco/repo/domain/hibernate/AppliedPatch.hbm.xml index 052088eb21..427b826b3c 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/AppliedPatch.hbm.xml +++ b/source/java/org/alfresco/repo/domain/hibernate/AppliedPatch.hbm.xml @@ -9,7 +9,7 @@ parentAssocs = contentNode.getParentAssocs(); @@ -362,7 +305,6 @@ public class HibernateNodeTest extends BaseSpringTest Map checkProperties = checkNode.getProperties(); assertTrue("Propery map retrieved was not the same instance", checkProperties == properties); assertTrue("Property not found", checkProperties.containsKey(ContentModel.PROP_NAME)); -// assertTrue("Property value instance retrieved not the same", checkProperties) flushAndClear(); // commit the transaction @@ -380,8 +322,6 @@ public class HibernateNodeTest extends BaseSpringTest assertNotNull(checkNode); checkAspects = checkNode.getAspects(); -// assertTrue("Node retrieved was not same instance", checkNode == node); - txn.commit(); } catch (Throwable e) @@ -448,6 +388,8 @@ public class HibernateNodeTest extends BaseSpringTest assoc1.setIsPrimary(true); assoc1.setTypeQName(QName.createQName(null, "type1")); assoc1.setQname(QName.createQName(null, "number1")); + assoc1.setChildNodeName("number1"); + assoc1.setChildNodeNameCrc(1); assoc1.buildAssociation(containerNode, contentNode1); getSession().save(assoc1); // create an association to content 2 @@ -455,6 +397,8 @@ public class HibernateNodeTest extends BaseSpringTest assoc2.setIsPrimary(true); assoc2.setTypeQName(QName.createQName(null, "type2")); assoc2.setQname(QName.createQName(null, "number2")); + assoc2.setChildNodeName("number2"); + assoc2.setChildNodeNameCrc(2); assoc2.buildAssociation(containerNode, contentNode2); getSession().save(assoc2); @@ -465,37 +409,10 @@ public class HibernateNodeTest extends BaseSpringTest // now read the structure back in from the container down containerNodeStatus = (NodeStatus) getSession().get(NodeStatusImpl.class, containerNodeKey); containerNode = containerNodeStatus.getNode(); - Collection assocs = containerNode.getChildAssocs(); - for (ChildAssoc assoc : assocs) - { - Node childNode = assoc.getChild(); - Store store = childNode.getStore(); - childNode.getAspects().size(); - childNode.getProperties().size(); - childNode.getParentAssocs().size(); - childNode.getChildAssocs().size(); - childNode.getSourceNodeAssocs().size(); - childNode.getTargetNodeAssocs().size(); - DbAccessControlList acl = childNode.getAccessControlList(); - if (acl != null) - { - acl.getEntries().size(); - } - } // clear out again getSession().clear(); - // now remove a property from each child - containerNodeStatus = (NodeStatus) getSession().get(NodeStatusImpl.class, containerNodeKey); - containerNode = containerNodeStatus.getNode(); - assocs = containerNode.getChildAssocs(); - for (ChildAssoc assoc : assocs) - { - Node childNode = assoc.getChild(); - PropertyValue removed = childNode.getProperties().remove(ContentModel.PROP_ARCHIVED_BY); - assertNotNull("Property was not present", removed); - } // expect that just the specific property gets removed in the delete statement getSession().flush(); getSession().clear(); @@ -510,6 +427,8 @@ public class HibernateNodeTest extends BaseSpringTest assoc3.setIsPrimary(false); assoc3.setTypeQName(QName.createQName(null, "type3")); assoc3.setQname(QName.createQName(null, "number3")); + assoc3.setChildNodeName("number3"); + assoc3.setChildNodeNameCrc(2); assoc3.buildAssociation(containerNode, contentNode2); // check whether the children are pulled in for this getSession().save(assoc3); diff --git a/source/java/org/alfresco/repo/domain/hibernate/Node.hbm.xml b/source/java/org/alfresco/repo/domain/hibernate/Node.hbm.xml index 51e8fb8fc6..79485306d3 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/Node.hbm.xml +++ b/source/java/org/alfresco/repo/domain/hibernate/Node.hbm.xml @@ -11,7 +11,7 @@ - - - - - - - - - - - - - - - - + + table="alf_child_assoc" > - - - - - - - - - + + + + + + + + + + + + + @@ -199,30 +171,32 @@ + table="alf_node_assoc" > - - - - - - - - - + + + + + + + + + + + @@ -232,39 +206,107 @@ org.alfresco.repo.domain.hibernate.StoreImpl as store + + update + org.alfresco.repo.domain.hibernate.ChildAssocImpl assoc + set + assoc.childNodeName = :newName, + assoc.childNodeNameCrc = :newNameCrc + where + assoc.id = :childAssocId + + + + select + assoc + from + org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc + where + assoc.parent.id = :parentId + order by + assoc.index, + assoc.id + + + + select + assoc + from + org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc + where + assoc.parent.id = :parentId and + assoc.typeQName = :typeQName and + assoc.childNodeName = :childNodeName and + assoc.childNodeNameCrc = :childNodeNameCrc + order by + assoc.index, + assoc.id + + + + select + assoc.typeQName, + assoc.qname, + assoc.isPrimary, + assoc.index, + child.id, + child.store.key.protocol, + child.store.key.identifier, + child.uuid as parentUuid + from + org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc + join assoc.parent as parent + join assoc.child as child + where + assoc.parent.id = :parentId + order by + assoc.index, + assoc.id + + select assoc from org.alfresco.repo.domain.hibernate.NodeAssocImpl as assoc where - assoc.source = :sourceNode and - assoc.target = :targetNode and + assoc.source.id = :sourceId and + assoc.target.id = :targetId and assoc.typeQName = :assocTypeQName - + select - target + assoc from org.alfresco.repo.domain.hibernate.NodeAssocImpl as assoc - join assoc.target as target where - assoc.source = :sourceNode and - assoc.typeQName = :assocTypeQName + assoc.source.id = :nodeId or + assoc.target.id = :nodeId - + select - source + assoc from org.alfresco.repo.domain.hibernate.NodeAssocImpl as assoc join assoc.source as source + join assoc.target as target where - assoc.target = :targetNode and - assoc.typeQName = :assocTypeQName + assoc.source.id = :sourceId - + + + select + assoc + from + org.alfresco.repo.domain.hibernate.NodeAssocImpl as assoc + join assoc.source as source + join assoc.target as target + where + assoc.target.id = :targetId + + select distinct status.changeTxnId @@ -333,4 +375,20 @@ node.properties.multiValued = false + + :lastAssocId and + assoc.typeQName = :assocTypeQName + order by + assoc.id + ]]> + + diff --git a/source/java/org/alfresco/repo/domain/hibernate/NodeAssocImpl.java b/source/java/org/alfresco/repo/domain/hibernate/NodeAssocImpl.java index 0d9bf82d9c..10aab749ee 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/NodeAssocImpl.java +++ b/source/java/org/alfresco/repo/domain/hibernate/NodeAssocImpl.java @@ -57,17 +57,6 @@ public class NodeAssocImpl implements NodeAssoc, Serializable // add the forward associations this.setTarget(targetNode); this.setSource(sourceNode); - // add the inverse associations - sourceNode.getTargetNodeAssocs().add(this); - targetNode.getSourceNodeAssocs().add(this); - } - - public void removeAssociation() - { - // maintain inverse assoc from source node to this instance - this.getSource().getTargetNodeAssocs().remove(this); - // maintain inverse assoc from target node to this instance - this.getTarget().getSourceNodeAssocs().remove(this); } public AssociationRef getNodeAssocRef() diff --git a/source/java/org/alfresco/repo/domain/hibernate/NodeImpl.java b/source/java/org/alfresco/repo/domain/hibernate/NodeImpl.java index 40aa060edd..2336570da7 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/NodeImpl.java +++ b/source/java/org/alfresco/repo/domain/hibernate/NodeImpl.java @@ -29,7 +29,6 @@ import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; import org.alfresco.repo.domain.ChildAssoc; import org.alfresco.repo.domain.DbAccessControlList; import org.alfresco.repo.domain.Node; -import org.alfresco.repo.domain.NodeAssoc; import org.alfresco.repo.domain.PropertyValue; import org.alfresco.repo.domain.Store; import org.alfresco.service.cmr.repository.NodeRef; @@ -52,12 +51,8 @@ public class NodeImpl extends LifecycleAdapter implements Node, Serializable private Store store; private String uuid; private QName typeQName; -// private NodeStatus status; private Set aspects; - private Collection sourceNodeAssocs; - private Collection targetNodeAssocs; private Collection parentAssocs; - private Collection childAssocs; private Map properties; private DbAccessControlList accessControlList; @@ -68,10 +63,7 @@ public class NodeImpl extends LifecycleAdapter implements Node, Serializable public NodeImpl() { aspects = new HashSet(5); - sourceNodeAssocs = new HashSet(5); - targetNodeAssocs = new HashSet(5); parentAssocs = new HashSet(5); - childAssocs = new HashSet(11); properties = new HashMap(5); ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); @@ -137,8 +129,14 @@ public class NodeImpl extends LifecycleAdapter implements Node, Serializable return false; } Node that = (Node) obj; - return (EqualsHelper.nullSafeEquals(getStore(), that.getStore()) - && EqualsHelper.nullSafeEquals(getUuid(), that.getUuid())); + if (EqualsHelper.nullSafeEquals(id, that.getId())) + { + return true; + } + else + { + return (this.getNodeRef().equals(that.getNodeRef())); + } } public int hashCode() @@ -221,16 +219,6 @@ public class NodeImpl extends LifecycleAdapter implements Node, Serializable this.typeQName = typeQName; } -// public NodeStatus getStatus() -// { -// return status; -// } -// -// public void setStatus(NodeStatus status) -// { -// this.status = status; -// } -// public Set getAspects() { return aspects; @@ -245,34 +233,6 @@ public class NodeImpl extends LifecycleAdapter implements Node, Serializable this.aspects = aspects; } - public Collection getSourceNodeAssocs() - { - return sourceNodeAssocs; - } - - /** - * For Hibernate use - */ - @SuppressWarnings("unused") - private void setSourceNodeAssocs(Collection sourceNodeAssocs) - { - this.sourceNodeAssocs = sourceNodeAssocs; - } - - public Collection getTargetNodeAssocs() - { - return targetNodeAssocs; - } - - /** - * For Hibernate use - */ - @SuppressWarnings("unused") - private void setTargetNodeAssocs(Collection targetNodeAssocs) - { - this.targetNodeAssocs = targetNodeAssocs; - } - public Collection getParentAssocs() { return parentAssocs; @@ -287,20 +247,6 @@ public class NodeImpl extends LifecycleAdapter implements Node, Serializable this.parentAssocs = parentAssocs; } - public Collection getChildAssocs() - { - return childAssocs; - } - - /** - * For Hibernate use - */ - @SuppressWarnings("unused") - private void setChildAssocs(Collection childAssocs) - { - this.childAssocs = childAssocs; - } - public Map getProperties() { return properties; diff --git a/source/java/org/alfresco/repo/domain/hibernate/Permission.hbm.xml b/source/java/org/alfresco/repo/domain/hibernate/Permission.hbm.xml index 8c0490ff4a..c79ac4586d 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/Permission.hbm.xml +++ b/source/java/org/alfresco/repo/domain/hibernate/Permission.hbm.xml @@ -8,7 +8,7 @@ System - true diff --git a/source/java/org/alfresco/repo/jscript/Actions.java b/source/java/org/alfresco/repo/jscript/Actions.java new file mode 100644 index 0000000000..818f52d130 --- /dev/null +++ b/source/java/org/alfresco/repo/jscript/Actions.java @@ -0,0 +1,334 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.jscript; + +import java.io.Serializable; +import java.util.List; +import java.util.Map; + +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ActionDefinition; +import org.alfresco.service.cmr.action.ActionService; +import org.alfresco.service.cmr.action.ParameterDefinition; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.namespace.QName; +import org.mozilla.javascript.Scriptable; + + +/** + * Scripted Action service for describing and executing actions against Nodes. + * + * @author davidc + */ +public final class Actions implements Scopeable +{ + /** Repository Service Registry */ + private ServiceRegistry services; + + /** Root scope for this object */ + private Scriptable scope; + + + /** + * Constructor + * + * @param services repository service registry + */ + public Actions(ServiceRegistry services) + { + this.services = services; + } + + /** + * @see org.alfresco.repo.jscript.Scopeable#setScope(org.mozilla.javascript.Scriptable) + */ + public void setScope(Scriptable scope) + { + this.scope = scope; + } + + /** + * Gets the list of registered action names + * + * @return the registered action names + */ + public String[] getRegistered() + { + ActionService actionService = services.getActionService(); + List defs = actionService.getActionDefinitions(); + String[] registered = new String[defs.size()]; + int i = 0; + for (ActionDefinition def : defs) + { + registered[i++] = def.getName(); + } + return registered; + } + + public String[] jsGet_registered() + { + return getRegistered(); + } + + /** + * Create an Action + * + * @param actionName the action name + * @return the action + */ + public ScriptAction create(String actionName) + { + ScriptAction scriptAction = null; + ActionService actionService = services.getActionService(); + ActionDefinition actionDef = actionService.getActionDefinition(actionName); + if (actionDef != null) + { + Action action = actionService.createAction(actionName); + scriptAction = new ScriptAction(action, actionDef); + scriptAction.setScope(scope); + } + return scriptAction; + } + + + /** + * Scriptable Action + * + * @author davidc + */ + public final class ScriptAction implements Serializable, Scopeable + { + private static final long serialVersionUID = 5794161358406531996L; + + /** Root scope for this object */ + private Scriptable scope; + + /** Converter with knowledge of action parameter values */ + private ActionValueConverter converter; + + /** Action state */ + private Action action; + private ActionDefinition actionDef; + private ScriptableParameterMap parameters = null; + + + /** + * Construct + * + * @param action Alfresco action + */ + public ScriptAction(Action action, ActionDefinition actionDef) + { + this.action = action; + this.actionDef = actionDef; + this.converter = new ActionValueConverter(); + } + + /** + * @see org.alfresco.repo.jscript.Scopeable#setScope(org.mozilla.javascript.Scriptable) + */ + public void setScope(Scriptable scope) + { + this.scope = scope; + } + + /** + * Returns the action name + * + * @return action name + */ + public String getName() + { + return this.actionDef.getName(); + } + + public String jsGet_name() + { + return getName(); + } + + /** + * Return all the properties known about this node. + * + * The Map returned implements the Scriptable interface to allow access to the properties via + * JavaScript associative array access. This means properties of a node can be access thus: + * node.properties["name"] + * + * @return Map of properties for this Node. + */ + @SuppressWarnings("synthetic-access") + public Map getParameters() + { + if (this.parameters == null) + { + // this Map implements the Scriptable interface for native JS syntax property access + this.parameters = new ScriptableParameterMap(); + Map actionParams = this.action.getParameterValues(); + for (Map.Entry entry : actionParams.entrySet()) + { + String name = entry.getKey(); + this.parameters.put(name, converter.convertActionParamForScript(name, entry.getValue())); + } + this.parameters.setModified(false); + } + return this.parameters; + } + + public Map jsGet_parameters() + { + return getParameters(); + } + + /** + * Execute action + * + * @param node the node to execute action upon + */ + @SuppressWarnings("synthetic-access") + public void execute(Node node) + { + if (this.parameters.isModified()) + { + Map actionParams = action.getParameterValues(); + actionParams.clear(); + + for (Map.Entry entry : this.parameters.entrySet()) + { + // perform the conversion from script wrapper object to repo serializable values + String name = entry.getKey(); + Serializable value = converter.convertActionParamForRepo(name, entry.getValue()); + actionParams.put(name, value); + } + } + services.getActionService().executeAction(action, node.getNodeRef()); + } + + /** + * Value converter with specific knowledge of action parameters + * + * @author davidc + */ + private class ActionValueConverter extends ValueConverter + { + /** + * Convert Action Parameter for Script usage + * + * @param paramName parameter name + * @param value value to convert + * @return converted value + */ + @SuppressWarnings("synthetic-access") + public Serializable convertActionParamForScript(String paramName, Serializable value) + { + ParameterDefinition paramDef = actionDef.getParameterDefintion(paramName); + if (paramDef != null && paramDef.getType().equals(DataTypeDefinition.QNAME)) + { + return ((QName)value).toPrefixString(services.getNamespaceService()); + } + else + { + return convertValueForScript(services, scope, null, value); + } + } + + /** + * Convert Action Parameter for Java usage + * + * @param paramName parameter name + * @param value value to convert + * @return converted value + */ + @SuppressWarnings("synthetic-access") + public Serializable convertActionParamForRepo(String paramName, Serializable value) + { + ParameterDefinition paramDef = actionDef.getParameterDefintion(paramName); + if (paramDef != null && paramDef.getType().equals(DataTypeDefinition.QNAME)) + { + return QName.createQName((String)value, services.getNamespaceService()); + } + else + { + return convertValueForRepo(value); + } + } + } + } + + + /** + * Scripted Parameter map with modified flag. + * + * @author davidc + */ + public static final class ScriptableParameterMap extends ScriptableHashMap + { + private static final long serialVersionUID = 574661815973241554L; + private boolean modified = false; + + + /** + * Is this a modified parameter map? + * + * @return true => modified + */ + /*package*/ boolean isModified() + { + return modified; + } + + /** + * Set explicitly whether this map is modified + * + * @param modified true => modified, false => not modified + */ + /*package*/ void setModified(boolean modified) + { + this.modified = modified; + } + + /* (non-Javadoc) + * @see org.mozilla.javascript.Scriptable#getClassName() + */ + @Override + public String getClassName() + { + return "ScriptableParameterMap"; + } + + /* (non-Javadoc) + * @see org.mozilla.javascript.Scriptable#delete(java.lang.String) + */ + @Override + public void delete(String name) + { + super.delete(name); + setModified(true); + } + + /* (non-Javadoc) + * @see org.mozilla.javascript.Scriptable#put(java.lang.String, org.mozilla.javascript.Scriptable, java.lang.Object) + */ + @Override + public void put(String name, Scriptable start, Object value) + { + super.put(name, start, value); + setModified(true); + } + } + +} diff --git a/source/java/org/alfresco/repo/jscript/Node.java b/source/java/org/alfresco/repo/jscript/Node.java index 2d7940249c..25226c1629 100644 --- a/source/java/org/alfresco/repo/jscript/Node.java +++ b/source/java/org/alfresco/repo/jscript/Node.java @@ -20,8 +20,6 @@ import java.io.Serializable; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -30,7 +28,11 @@ import java.util.StringTokenizer; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; +import org.alfresco.repo.action.executer.TransformActionExecuter; +import org.alfresco.repo.content.transform.magick.ImageMagickContentTransformer; import org.alfresco.repo.security.permissions.AccessDeniedException; +import org.alfresco.repo.template.FreeMarkerProcessor; +import org.alfresco.repo.version.VersionModel; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.dictionary.InvalidAspectException; @@ -44,18 +46,21 @@ import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.InvalidNodeRefException; +import org.alfresco.service.cmr.repository.NoTransformerException; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.TemplateImageResolver; +import org.alfresco.service.cmr.repository.TemplateNode; import org.alfresco.service.cmr.security.AccessStatus; import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.cmr.version.Version; +import org.alfresco.service.cmr.version.VersionType; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.RegexQNamePattern; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.mozilla.javascript.Context; -import org.mozilla.javascript.NativeArray; +import org.mozilla.javascript.Scriptable; import org.mozilla.javascript.ScriptableObject; import org.mozilla.javascript.Wrapper; import org.springframework.util.StringUtils; @@ -72,7 +77,7 @@ import org.springframework.util.StringUtils; * * @author Kevin Roast */ -public final class Node implements Serializable +public class Node implements Serializable, Scopeable { private static Log logger = LogFactory.getLog(Node.class); @@ -81,32 +86,37 @@ public final class Node implements Serializable private final static String CONTENT_PROP_URL = "/download/direct/{0}/{1}/{2}/{3}?property={4}"; private final static String FOLDER_BROWSE_URL = "/navigate/browse/{0}/{1}/{2}"; - /** The children of this node */ - private Node[] children = null; + /** Root scope for this object */ + private Scriptable scope; - /** The associations from this node */ - private ScriptableQNameMap assocs = null; + /** Node Value Converter */ + private NodeValueConverter converter = null; /** Cached values */ private NodeRef nodeRef; private String name; private QName type; private String id; + /** The aspects applied to this node */ private Set aspects = null; + /** The associations from this node */ + private ScriptableQNameMap assocs = null; + /** The children of this node */ + private Node[] children = null; + /** The properties of this node */ private ScriptableQNameMap properties = null; private ServiceRegistry services = null; private NodeService nodeService = null; private Boolean isDocument = null; private Boolean isContainer = null; private String displayPath = null; - private String mimetype = null; - private Long size = null; private TemplateImageResolver imageResolver = null; private Node parent = null; private ChildAssociationRef primaryParentAssoc = null; // NOTE: see the reset() method when adding new cached members! + // ------------------------------------------------------------------------------ // Construction @@ -118,6 +128,19 @@ public final class Node implements Serializable * @param resolver Image resolver to use to retrieve icons */ public Node(NodeRef nodeRef, ServiceRegistry services, TemplateImageResolver resolver) + { + this(nodeRef, services, resolver, null); + } + + /** + * Constructor + * + * @param nodeRef The NodeRef this Node wrapper represents + * @param services The ServiceRegistry the Node can use to access services + * @param resolver Image resolver to use to retrieve icons + * @param scope Root scope for this Node + */ + public Node(NodeRef nodeRef, ServiceRegistry services, TemplateImageResolver resolver, Scriptable scope) { if (nodeRef == null) { @@ -134,6 +157,15 @@ public final class Node implements Serializable this.services = services; this.nodeService = services.getNodeService(); this.imageResolver = resolver; + this.scope = scope; + } + + /** + * @see org.alfresco.repo.jscript.Scopeable#setScope(org.mozilla.javascript.Scriptable) + */ + public void setScope(Scriptable scope) + { + this.scope = scope; } @@ -247,7 +279,8 @@ public final class Node implements Serializable for (int i=0; i props = new HashMap(getProperties().size()); for (String key : this.properties.keySet()) { Serializable value = (Serializable)this.properties.get(key); // perform the conversion from script wrapper object to repo serializable values - value = convertValue(value); + value = getValueConverter().convertValueForRepo(value); props.put(createQName(key), value); } @@ -860,69 +909,38 @@ public final class Node implements Serializable } /** - * Convert an object from any script wrapper value to a valid repository serializable value. - * This includes converting JavaScript Array objects to Lists of valid objects. + * Re-sets the type of the node. Can be called in order specialise a node to a sub-type. * - * @param value Value to convert from script wrapper object to repo serializable value + * This should be used with caution since calling it changes the type of the node and thus + * implies a different set of aspects, properties and associations. It is the responsibility + * of the caller to ensure that the node is in a approriate state after changing the type. * - * @return valid repo value + * @param type Type to specialize the node + * + * @return true if successful, false otherwise */ - private static Serializable convertValue(Serializable value) + public boolean specializeType(String type) { - if (value instanceof Node) + QName qnameType = createQName(type); + + // Ensure that we are performing a specialise + if (getType().equals(qnameType) == false && + this.services.getDictionaryService().isSubClass(qnameType, getType()) == true) { - // convert back to NodeRef - value = ((Node)value).getNodeRef(); - } - else if (value instanceof ScriptContentData) - { - // convert back to ContentData - value = ((ScriptContentData)value).contentData; - } - else if (value instanceof Wrapper) - { - // unwrap a Java object from a JavaScript wrapper - // recursively call this method to convert the unwrapped value - value = convertValue((Serializable)((Wrapper)value).unwrap()); - } - else if (value instanceof ScriptableObject) - { - // a scriptable object will probably indicate a multi-value property - // set using a JavaScript Array object - ScriptableObject values = (ScriptableObject)value; - - if (value instanceof NativeArray) + // Specialise the type of the node + try { - // convert JavaScript array of values to a List of Serializable objects - Object[] propIds = values.getIds(); - List propValues = new ArrayList(propIds.length); - for (int i=0; i aspectProps = null; - if (properties instanceof ScriptableObject) + if (props instanceof ScriptableObject) { - ScriptableObject props = (ScriptableObject)properties; + ScriptableObject properties = (ScriptableObject)props; + // we need to get all the keys to the properties provided // and convert them to a Map of QName to Serializable objects - Object[] propIds = props.getIds(); + Object[] propIds = properties.getIds(); aspectProps = new HashMap(propIds.length); for (int i=0; i props = new HashMap(2, 1.0f); + props.put(Version.PROP_DESCRIPTION, history); + props.put(VersionModel.PROP_VERSION_TYPE, majorVersion ? VersionType.MAJOR : VersionType.MINOR); + NodeRef original = this.services.getCheckOutCheckInService().checkin(this.nodeRef, props); + return new Node(original, this.services, this.imageResolver, this.scope); + } + + /** + * Cancel the check-out of a working copy document. The working copy will be deleted and any + * changes made to it are lost. Note that this method can only be called on a working copy Node. + * The reference to this working copy Node should be discarded. + * + * @return the original Node that was checked out. + */ + public Node cancelCheckout() + { + NodeRef original = this.services.getCheckOutCheckInService().cancelCheckout(this.nodeRef); + return new Node(original, this.services, this.imageResolver, this.scope); + } + + + // ------------------------------------------------------------------------------ + // Transformation and Rendering API + + /** + * Transform a document to a new document mimetype format. A copy of the document is made and + * the extension changed to match the new mimetype, then the transformation is applied. + * + * @param mimetype Mimetype destination for the transformation + * + * @return Node representing the newly transformed document. + */ + public Node transformDocument(String mimetype) + { + return transformDocument(mimetype, getPrimaryParentAssoc().getParentRef()); + } + + /** + * Transform a document to a new document mimetype format. A copy of the document is made in the + * specified destination folder and the extension changed to match the new mimetype, then then + * transformation is applied. + * + * @param mimetype Mimetype destination for the transformation + * @param destination Destination folder location + * + * @return Node representing the newly transformed document. + */ + public Node transformDocument(String mimetype, Node destination) + { + return transformDocument(mimetype, destination.getNodeRef()); + } + + private Node transformDocument(String mimetype, NodeRef destination) + { + // the delegate definition for transforming a document + Transformer transformer = new Transformer() + { + public Node transform(ContentService contentService, NodeRef nodeRef, ContentReader reader, ContentWriter writer) + { + Node transformedNode = null; + if (contentService.isTransformable(reader, writer)) + { + try + { + contentService.transform(reader, writer); + transformedNode = new Node(nodeRef, services, imageResolver, scope); + } + catch (NoTransformerException err) + { + // failed to find a useful transformer - do not return a node instance + } + } + return transformedNode; + } + }; + + return transformNode(transformer, mimetype, destination); + } + + /** + * Generic method to transform Node content from one mimetype to another. + * + * @param transformer The Transformer delegate supplying the transformation logic + * @param mimetype Mimetype of the destination content + * @param destination Destination folder location for the resulting document + * + * @return Node representing the transformed content - or null if the transform failed + */ + private Node transformNode(Transformer transformer, String mimetype, NodeRef destination) + { + Node transformedNode = null; + + // get the content reader + ContentService contentService = this.services.getContentService(); + ContentReader reader = contentService.getReader(this.nodeRef, ContentModel.PROP_CONTENT); + + // only perform the transformation if some content is available + if (reader != null) + { + // Copy the content node to a new node + String copyName = TransformActionExecuter.transformName( + this.services.getMimetypeService(), getName(), mimetype); + NodeRef copyNodeRef = this.services.getCopyService().copy( + this.nodeRef, + destination, + ContentModel.ASSOC_CONTAINS, + QName.createQName( + ContentModel.PROP_CONTENT.getNamespaceURI(), + QName.createValidLocalName(copyName)), + false); + + // modify the name of the copy to reflect the new mimetype + this.nodeService.setProperty( + copyNodeRef, + ContentModel.PROP_NAME, + copyName); + + // get the writer and set it up + ContentWriter writer = contentService.getWriter(copyNodeRef, ContentModel.PROP_CONTENT, true); + writer.setMimetype(mimetype); // new mimetype + writer.setEncoding(reader.getEncoding()); // original encoding + + // Try and transform the content using the supplied delegate + transformedNode = transformer.transform(contentService, copyNodeRef, reader, writer); + } + + return transformedNode; + } + + /** + * Transform an image to a new image format. A copy of the image document is made and + * the extension changed to match the new mimetype, then the transformation is applied. + * + * @param mimetype Mimetype destination for the transformation + * + * @return Node representing the newly transformed image. + */ + public Node transformImage(String mimetype) + { + return transformImage(mimetype, null, getPrimaryParentAssoc().getParentRef()); + } + + /** + * Transform an image to a new image format. A copy of the image document is made and + * the extension changed to match the new mimetype, then the transformation is applied. + * + * @param mimetype Mimetype destination for the transformation + * @param options Image convert command options + * + * @return Node representing the newly transformed image. + */ + public Node transformImage(String mimetype, String options) + { + return transformImage(mimetype, options, getPrimaryParentAssoc().getParentRef()); + } + + /** + * Transform an image to a new image mimetype format. A copy of the image document is made in the + * specified destination folder and the extension changed to match the new mimetype, then then + * transformation is applied. + * + * @param mimetype Mimetype destination for the transformation + * @param destination Destination folder location + * + * @return Node representing the newly transformed image. + */ + public Node transformImage(String mimetype, Node destination) + { + return transformImage(mimetype, null, destination.getNodeRef()); + } + + /** + * Transform an image to a new image mimetype format. A copy of the image document is made in the + * specified destination folder and the extension changed to match the new mimetype, then then + * transformation is applied. + * + * @param mimetype Mimetype destination for the transformation + * @param options Image convert command options + * @param destination Destination folder location + * + * @return Node representing the newly transformed image. + */ + public Node transformImage(String mimetype, String options, Node destination) + { + return transformImage(mimetype, options, destination.getNodeRef()); + } + + private Node transformImage(String mimetype, final String options, NodeRef destination) + { + // the delegate definition for transforming an image + Transformer transformer = new Transformer() + { + public Node transform(ContentService contentService, NodeRef nodeRef, ContentReader reader, ContentWriter writer) + { + Node transformedNode = null; + try + { + Map opts = new HashMap(1); + opts.put(ImageMagickContentTransformer.KEY_OPTIONS, options != null ? options : ""); + contentService.getImageTransformer().transform(reader, writer, opts); + transformedNode = new Node(nodeRef, services, imageResolver, scope); + } + catch (NoTransformerException err) + { + // failed to find a useful transformer - do not return a node instance + } + return transformedNode; + } + }; + + return transformNode(transformer, mimetype, destination); + } + + /** + * Process a FreeMarker Template against the current node. + * + * @param template Node of the template to execute + * + * @return output of the template execution + */ + public String processTemplate(Node template) + { + return processTemplate(template.getContent(), null, null); + } + + /** + * Process a FreeMarker Template against the current node. + * + * @param template Node of the template to execute + * @param args Scriptable object (generally an associative array) containing the + * name/value pairs of arguments to be passed to the template + * + * @return output of the template execution + */ + public String processTemplate(Node template, Object args) + { + return processTemplate(template.getContent(), null, (ScriptableObject)args); + } + + /** + * Process a FreeMarker Template against the current node. + * + * @param template The template to execute + * + * @return output of the template execution + */ + public String processTemplate(String template) + { + return processTemplate(template, null, null); + } + + /** + * Process a FreeMarker Template against the current node. + * + * @param template The template to execute + * @param args Scriptable object (generally an associative array) containing the + * name/value pairs of arguments to be passed to the template + * + * @return output of the template execution + */ + public String processTemplate(String template, Object args) + { + return processTemplate(template, null, (ScriptableObject)args); + } + + private String processTemplate(String template, NodeRef templateRef, ScriptableObject args) + { + // build default model for the template processing + Map model = FreeMarkerProcessor.buildDefaultModel(services, + ((Node)((Wrapper)scope.get("person", scope)).unwrap()).getNodeRef(), + ((Node)((Wrapper)scope.get("companyhome", scope)).unwrap()).getNodeRef(), + ((Node)((Wrapper)scope.get("userhome", scope)).unwrap()).getNodeRef(), + templateRef, + this.imageResolver); + + // add the current node as either the document/space as appropriate + if (this.isDocument()) + { + model.put("document", new TemplateNode(this.nodeRef, this.services, this.imageResolver)); + model.put("space", new TemplateNode(getPrimaryParentAssoc().getParentRef(), this.services, this.imageResolver)); + } + else + { + model.put("space", new TemplateNode(this.nodeRef, this.services, this.imageResolver)); + } + + // add the supplied args to the 'args' root object + if (args != null) + { + // we need to get all the keys to the properties provided + // and convert them to a Map of QName to Serializable objects + Object[] propIds = args.getIds(); + Map templateArgs = new HashMap(propIds.length); + for (int i=0; i(); + } + + // add useful util objects + model.put("actions", new Actions(services)); + model.put("logger", new ScriptLogger()); // insert supplied object model into root of the default scope - if (model != null) { for (String key : model.keySet()) { - Object jsObject = Context.javaToJS(model.get(key), scope); + // set the root scope on appropriate objects + // this is used to allow native JS object creation etc. + Object obj = model.get(key); + if (obj instanceof Scopeable) + { + ((Scopeable)obj).setScope(scope); + } + + // convert/wrap each object to JavaScript compatible + Object jsObject = Context.javaToJS(obj, scope); + + // insert into the root scope ready for access by the script ScriptableObject.putProperty(scope, key, jsObject); } } @@ -226,6 +244,12 @@ public class RhinoScriptService implements ScriptService // execute the script Object result = cx.evaluateReader(scope, reader, "AlfrescoScript", 1, null); + // extract java object result if wrapped by rhinoscript + if (result instanceof Wrapper) + { + result = ((Wrapper)result).unwrap(); + } + return result; } catch (Throwable err) @@ -234,7 +258,7 @@ public class RhinoScriptService implements ScriptService } finally { - cx.exit(); + Context.exit(); if (logger.isDebugEnabled()) { @@ -317,7 +341,6 @@ public class RhinoScriptService implements ScriptService model.put("space", new Node(space, services, resolver)); } - // add other useful util objects model.put("search", new Search(services, companyHome.getStoreRef(), resolver)); return model; diff --git a/source/java/org/alfresco/repo/jscript/RhinoScriptTest.java b/source/java/org/alfresco/repo/jscript/RhinoScriptTest.java index df3f5fe57c..76f26d93af 100644 --- a/source/java/org/alfresco/repo/jscript/RhinoScriptTest.java +++ b/source/java/org/alfresco/repo/jscript/RhinoScriptTest.java @@ -17,14 +17,13 @@ package org.alfresco.repo.jscript; import java.io.InputStream; -import java.io.Serializable; import java.util.HashMap; import java.util.List; import java.util.Map; import junit.framework.TestCase; -import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.model.ContentModel; import org.alfresco.repo.dictionary.DictionaryComponent; import org.alfresco.repo.dictionary.DictionaryDAO; import org.alfresco.repo.dictionary.M2Model; @@ -33,7 +32,6 @@ import org.alfresco.repo.security.authentication.AuthenticationComponent; import org.alfresco.repo.transaction.TransactionUtil; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.repository.ChildAssociationRef; -import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.NodeRef; @@ -267,7 +265,114 @@ public class RhinoScriptTest extends TestCase }); } + public void testScriptActions() + { + TransactionUtil.executeInUserTransaction( + transactionService, + new TransactionUtil.TransactionWork() + { + public Object doWork() throws Exception + { + StoreRef store = nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, "rhino_" + System.currentTimeMillis()); + NodeRef root = nodeService.getRootNode(store); + + try + { + // create a content object + ChildAssociationRef childRef = nodeService.createNode( + root, + BaseNodeServiceTest.ASSOC_TYPE_QNAME_TEST_CHILDREN, + QName.createQName(BaseNodeServiceTest.NAMESPACE, "script_content"), + BaseNodeServiceTest.TYPE_QNAME_TEST_CONTENT, + null); + NodeRef contentNodeRef = childRef.getChildRef(); + ContentWriter writer = contentService.getWriter( + contentNodeRef, + BaseNodeServiceTest.PROP_QNAME_TEST_CONTENT, + true); + writer.setMimetype("application/x-javascript"); + writer.putContent(TESTSCRIPT1); + + + // create an Alfresco scriptable Node object + // the Node object is a wrapper similar to the TemplateNode concept + Map model = new HashMap(); + model.put("doc", new Node(childRef.getChildRef(), serviceRegistry, null)); + model.put("root", new Node(root, serviceRegistry, null)); + + // execute to add aspect via action + Object result = scriptService.executeScript(TESTSCRIPT_CLASSPATH2, model); + System.out.println("Result from TESTSCRIPT_CLASSPATH2: " + result.toString()); + assertTrue((Boolean)result); // we know the result is a boolean + + // ensure aspect has been added via script + assertTrue(nodeService.hasAspect(childRef.getChildRef(), ContentModel.ASPECT_LOCKABLE)); + } + catch (Throwable err) + { + err.printStackTrace(); + fail(err.getMessage()); + } + + return null; + } + }); + } + + + public void xtestScriptActionsMail() + { + TransactionUtil.executeInUserTransaction( + transactionService, + new TransactionUtil.TransactionWork() + { + public Object doWork() throws Exception + { + StoreRef store = nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, "rhino_" + System.currentTimeMillis()); + NodeRef root = nodeService.getRootNode(store); + + try + { + // create a content object + ChildAssociationRef childRef = nodeService.createNode( + root, + BaseNodeServiceTest.ASSOC_TYPE_QNAME_TEST_CHILDREN, + QName.createQName(BaseNodeServiceTest.NAMESPACE, "script_content"), + BaseNodeServiceTest.TYPE_QNAME_TEST_CONTENT, + null); + NodeRef contentNodeRef = childRef.getChildRef(); + ContentWriter writer = contentService.getWriter( + contentNodeRef, + BaseNodeServiceTest.PROP_QNAME_TEST_CONTENT, + true); + writer.setMimetype("application/x-javascript"); + writer.putContent(TESTSCRIPT1); + + // create an Alfresco scriptable Node object + // the Node object is a wrapper similar to the TemplateNode concept + Map model = new HashMap(); + model.put("doc", new Node(childRef.getChildRef(), serviceRegistry, null)); + model.put("root", new Node(root, serviceRegistry, null)); + + // execute to add aspect via action + Object result = scriptService.executeScript(TESTSCRIPT_CLASSPATH3, model); + System.out.println("Result from TESTSCRIPT_CLASSPATH3: " + result.toString()); + assertTrue((Boolean)result); // we know the result is a boolean + } + catch (Throwable err) + { + err.printStackTrace(); + fail(err.getMessage()); + } + + return null; + } + }); + } + private static final String TESTSCRIPT_CLASSPATH1 = "org/alfresco/repo/jscript/test_script1.js"; + private static final String TESTSCRIPT_CLASSPATH2 = "org/alfresco/repo/jscript/test_script2.js"; + private static final String TESTSCRIPT_CLASSPATH3 = "org/alfresco/repo/jscript/test_script3.js"; private static final String TESTSCRIPT1 = "var id = root.id;\r\n" + diff --git a/source/java/org/alfresco/repo/jscript/Scopeable.java b/source/java/org/alfresco/repo/jscript/Scopeable.java new file mode 100644 index 0000000000..17aae1adca --- /dev/null +++ b/source/java/org/alfresco/repo/jscript/Scopeable.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.jscript; + +import org.mozilla.javascript.Scriptable; + +/** + * Interface contract for objects that supporting setting of the global scripting scope. + * This is used to mark objects that are not themselves natively scriptable (i.e. they are + * wrapped Java objects) but need to access the global scope for the purposes of JavaScript + * object creation etc. + * + * @author Kevin Roast + */ +public interface Scopeable +{ + /** + * Set the Scriptable global scope + * + * @param scope global scope + */ + void setScope(Scriptable scope); +} diff --git a/source/java/org/alfresco/repo/jscript/ScriptLogger.java b/source/java/org/alfresco/repo/jscript/ScriptLogger.java new file mode 100644 index 0000000000..2f885ff474 --- /dev/null +++ b/source/java/org/alfresco/repo/jscript/ScriptLogger.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.jscript; + +import org.apache.log4j.Logger; + +/** + * @author Kevin Roast + */ +public final class ScriptLogger +{ + private static final Logger logger = Logger.getLogger(ScriptLogger.class); + + public boolean isLoggingEnabled() + { + return logger.isDebugEnabled(); + } + + public boolean jsGet_isLoggingEnabled() + { + return isLoggingEnabled(); + } + + public void log(String str) + { + logger.debug(str); + } +} diff --git a/source/java/org/alfresco/repo/jscript/ScriptableHashMap.java b/source/java/org/alfresco/repo/jscript/ScriptableHashMap.java index 95b2a72d6d..77bbba3483 100644 --- a/source/java/org/alfresco/repo/jscript/ScriptableHashMap.java +++ b/source/java/org/alfresco/repo/jscript/ScriptableHashMap.java @@ -16,17 +16,21 @@ */ package org.alfresco.repo.jscript; -import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; -import org.alfresco.service.namespace.NamespacePrefixResolver; -import org.alfresco.service.namespace.QNameMap; import org.mozilla.javascript.Scriptable; /** * @author Kevin Roast */ -public class ScriptableHashMap extends HashMap implements Scriptable +public class ScriptableHashMap extends LinkedHashMap implements Scriptable { + private static final long serialVersionUID = 3664761893203964569L; + + private Scriptable parentScope; + private Scriptable prototype; + /** * @see org.mozilla.javascript.Scriptable#getClassName() */ @@ -56,7 +60,14 @@ public class ScriptableHashMap extends HashMap implements Scriptable */ public Object get(int index, Scriptable start) { - return null; + Object value = null; + int i=0; + Iterator itrValues = this.values().iterator(); + while (i++ <= index && itrValues.hasNext()) + { + value = itrValues.next(); + } + return value; } /** @@ -73,16 +84,17 @@ public class ScriptableHashMap extends HashMap implements Scriptable */ public boolean has(int index, Scriptable start) { - return false; + return (index >= 0 && this.values().size() > index); } /** * @see org.mozilla.javascript.Scriptable#put(java.lang.String, org.mozilla.javascript.Scriptable, java.lang.Object) */ + @SuppressWarnings("unchecked") public void put(String name, Scriptable start, Object value) { // add the property to the underlying QName map - put(name, value); + put((K)name, (V)value); } /** @@ -90,6 +102,7 @@ public class ScriptableHashMap extends HashMap implements Scriptable */ public void put(int index, Scriptable start, Object value) { + // TODO: implement? } /** @@ -106,6 +119,17 @@ public class ScriptableHashMap extends HashMap implements Scriptable */ public void delete(int index) { + int i=0; + Iterator itrKeys = this.keySet().iterator(); + while (i <= index && itrKeys.hasNext()) + { + Object key = itrKeys.next(); + if (i == index) + { + remove(key); + break; + } + } } /** @@ -113,7 +137,7 @@ public class ScriptableHashMap extends HashMap implements Scriptable */ public Scriptable getPrototype() { - return null; + return this.prototype; } /** @@ -121,6 +145,7 @@ public class ScriptableHashMap extends HashMap implements Scriptable */ public void setPrototype(Scriptable prototype) { + this.prototype = prototype; } /** @@ -128,7 +153,7 @@ public class ScriptableHashMap extends HashMap implements Scriptable */ public Scriptable getParentScope() { - return null; + return this.parentScope; } /** @@ -136,6 +161,7 @@ public class ScriptableHashMap extends HashMap implements Scriptable */ public void setParentScope(Scriptable parent) { + this.parentScope = parent; } /** @@ -143,7 +169,7 @@ public class ScriptableHashMap extends HashMap implements Scriptable */ public Object[] getIds() { - return null; + return keySet().toArray(); } /** diff --git a/source/java/org/alfresco/repo/jscript/Search.java b/source/java/org/alfresco/repo/jscript/Search.java index ae89a2e178..79ee477ae5 100644 --- a/source/java/org/alfresco/repo/jscript/Search.java +++ b/source/java/org/alfresco/repo/jscript/Search.java @@ -17,9 +17,6 @@ package org.alfresco.repo.jscript; import java.io.StringReader; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; @@ -28,7 +25,6 @@ import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.repository.TemplateImageResolver; -import org.alfresco.service.cmr.repository.TemplateNode; import org.alfresco.service.cmr.search.ResultSet; import org.alfresco.service.cmr.search.ResultSetRow; import org.alfresco.service.cmr.search.SearchService; @@ -37,6 +33,7 @@ import org.apache.commons.logging.LogFactory; import org.dom4j.Document; import org.dom4j.Element; import org.dom4j.io.SAXReader; +import org.mozilla.javascript.Scriptable; /** * Search component for use by the ScriptService. @@ -51,7 +48,7 @@ import org.dom4j.io.SAXReader; * * @author Kevin Roast */ -public final class Search +public final class Search implements Scopeable { private static Log logger = LogFactory.getLog(Search.class); @@ -59,6 +56,9 @@ public final class Search private StoreRef storeRef; private TemplateImageResolver imageResolver; + /** Root scope for this object */ + private Scriptable scope; + /** * Constructor @@ -72,6 +72,48 @@ public final class Search this.imageResolver = imageResolver; } + /** + * @see org.alfresco.repo.jscript.Scopeable#setScope(org.mozilla.javascript.Scriptable) + */ + public void setScope(Scriptable scope) + { + this.scope = scope; + } + + /** + * Find a single Node by the Node reference + * + * @param ref The NodeRef of the Node to find + * + * @return the Node if found or null if failed to find + */ + public Node findNode(NodeRef ref) + { + return findNode(ref.toString()); + } + + /** + * Find a single Node by the Node reference + * + * @param ref The fully qualified NodeRef in String format + * + * @return the Node if found or null if failed to find + */ + public Node findNode(String ref) + { + String query = ref.replace(":", "\\:"); + query = query.replace("/", "\\/"); + Node[] result = query("ID:" + query); + if (result.length == 1) + { + return result[0]; + } + else + { + return null; + } + } + /** * Execute a Lucene search * @@ -171,7 +213,8 @@ public final class Search for (ResultSetRow row: results) { NodeRef nodeRef = row.getNodeRef(); - nodes[count++] = new Node(nodeRef, services, this.imageResolver); + nodes[count] = new Node(nodeRef, services, this.imageResolver, this.scope); + count++; } } } diff --git a/source/java/org/alfresco/repo/jscript/ValueConverter.java b/source/java/org/alfresco/repo/jscript/ValueConverter.java new file mode 100644 index 0000000000..9fb35452c9 --- /dev/null +++ b/source/java/org/alfresco/repo/jscript/ValueConverter.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.jscript; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.List; + +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.NativeArray; +import org.mozilla.javascript.ScriptRuntime; +import org.mozilla.javascript.Scriptable; +import org.mozilla.javascript.ScriptableObject; +import org.mozilla.javascript.Wrapper; + + +/** + * Value conversion allowing safe usage of values in Script and Java. + */ +public class ValueConverter +{ + + /** + * Convert an object from any repository serialized value to a valid script object. + * This includes converting Collection multi-value properties into JavaScript Array objects. + * + * @param services Repository Services Registry + * @param scope Scripting scope + * @param qname QName of the property value for conversion + * @param value Property value + * + * @return Value safe for scripting usage + */ + public Serializable convertValueForScript(ServiceRegistry services, Scriptable scope, QName qname, Serializable value) + { + // perform conversions from Java objects to JavaScript scriptable instances + if (value == null) + { + return null; + } + else if (value instanceof NodeRef) + { + // NodeRef object properties are converted to new Node objects + // so they can be used as objects within a template + value = new Node(((NodeRef)value), services, null, scope); + } + else if (value instanceof Date) + { + // convert Date to JavaScript native Date object + // call the "Date" constructor on the root scope object - passing in the millisecond + // value from the Java date - this will construct a JavaScript Date with the same value + Date date = (Date)value; + Object val = ScriptRuntime.newObject( + Context.getCurrentContext(), scope, "Date", new Object[] {date.getTime()}); + value = (Serializable)val; + } + else if (value instanceof Collection) + { + // recursively convert each value in the collection + Collection collection = (Collection)value; + Serializable[] array = new Serializable[collection.size()]; + int index = 0; + for (Serializable obj : collection) + { + array[index++] = convertValueForScript(services, scope, qname, obj); + } + value = array; + } + // simple numbers and strings are wrapped automatically by Rhino + + return value; + } + + + /** + * Convert an object from any script wrapper value to a valid repository serializable value. + * This includes converting JavaScript Array objects to Lists of valid objects. + * + * @param value Value to convert from script wrapper object to repo serializable value + * + * @return valid repo value + */ + public Serializable convertValueForRepo(Serializable value) + { + if (value == null) + { + return null; + } + else if (value instanceof Node) + { + // convert back to NodeRef + value = ((Node)value).getNodeRef(); + } + else if (value instanceof Wrapper) + { + // unwrap a Java object from a JavaScript wrapper + // recursively call this method to convert the unwrapped value + value = convertValueForRepo((Serializable)((Wrapper)value).unwrap()); + } + else if (value instanceof ScriptableObject) + { + // a scriptable object will probably indicate a multi-value property + // set using a JavaScript Array object + ScriptableObject values = (ScriptableObject)value; + + if (value instanceof NativeArray) + { + // convert JavaScript array of values to a List of Serializable objects + Object[] propIds = values.getIds(); + List propValues = new ArrayList(propIds.length); + for (int i=0; i list = new ArrayList(array.length); + for (int i=0; i children = nodeService.getChildAssocs(parentNodeRef); + Runnable runnable = new Runnable() + { + public void run() + { + // authenticate + authenticationComponent.setSystemUserAsCurrentUser(); + + for (int i = 0; i < repetitions; i++) + { + // read the contents of each folder + for (ChildAssociationRef childAssociationRef : children) + { + final NodeRef folderRef = childAssociationRef.getChildRef(); + TransactionWork readWork = new TransactionWork() + { + public Object doWork() throws Exception + { + // read the child associations of the folder + nodeService.getChildAssocs(folderRef); + // get the type + nodeService.getType(folderRef); + // done + return null; + }; + }; + TransactionUtil.executeInUserTransaction(transactionService, readWork, true); + } + } + } + }; + + // kick off the required number of threads + logger.debug("\n" + + "Starting " + threadCount + + " threads reading properties and children of " + children.size() + + " folder " + repetitions + + " times."); + ThreadGroup threadGroup = new ThreadGroup(getName()); + Thread[] threads = new Thread[threadCount]; + for (int i = 0; i < threadCount; i++) + { + threads[i] = new Thread(threadGroup, runnable, String.format("FileReader-%02d", i)); + threads[i].start(); + } + // join each thread so that we wait for them all to finish + for (int i = 0; i < threads.length; i++) + { + try + { + threads[i].join(); + } + catch (InterruptedException e) + { + // not too serious - the worker threads are non-daemon + } + } } + + public void test_1_ordered_1_10() throws Exception + { + buildStructure(rootFolderRef, 1, false, 1, 10, null); + } +// public void test_1_ordered_1_10_read() throws Exception +// { +// buildStructure(rootFolderRef, 1, false, 50, 1, null); +// readStructure(rootFolderRef, 50, 1000, null); +// } // // public void test_4_ordered_10_100() throws Exception // { @@ -250,24 +321,44 @@ public class FileFolderPerformanceTester extends TestCase // { // buildStructure(rootFolderRef, 4, true, 10, 100, new double[] {0.25, 0.50, 0.75}); // } - public void test_4_shuffled_100_100() throws Exception +// public void test_1_ordered_100_100() throws Exception +// { +// buildStructure( +// rootFolderRef, +// 1, +// false, +// 100, +// 100, +// new double[] {0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80, 0.90}); +// } +// public void test_1_shuffled_10_400() throws Exception +// { +// buildStructure( +// rootFolderRef, +// 1, +// true, +// 10, +// 400, +// new double[] {0.05, 0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80, 0.90}); +// } + public void test_4_shuffled_10_100() throws Exception { buildStructure( rootFolderRef, 4, true, + 10, 100, - 100, - new double[] {0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80, 0.90}); + new double[] {0.05, 0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80, 0.90}); } -// public void test_4_shuffled_1000_1000() throws Exception +// public void test_1_ordered_1_50000() throws Exception // { // buildStructure( // rootFolderRef, -// 4, -// true, -// 1000, -// 1000, -// new double[] {0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80, 0.90}); +// 1, +// false, +// 1, +// 50000, +// new double[] {0.01, 0.02, 0.03, 0.04, 0.05, 0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80, 0.90}); // } } diff --git a/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java b/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java index aefc09f3e1..18b1922b4e 100644 --- a/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java +++ b/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java @@ -39,12 +39,16 @@ import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.CopyService; +import org.alfresco.service.cmr.repository.DuplicateChildNodeNameException; import org.alfresco.service.cmr.repository.InvalidNodeRefException; import org.alfresco.service.cmr.repository.MimetypeService; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.Path; import org.alfresco.service.cmr.search.QueryParameterDefinition; +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.NamespaceService; import org.alfresco.service.namespace.QName; @@ -59,24 +63,30 @@ import org.apache.commons.logging.LogFactory; */ public class FileFolderServiceImpl implements FileFolderService { - /** Shallow search for all files */ - private static final String XPATH_QUERY_SHALLOW_FILES = - "./*" + - "[(subtypeOf('" + ContentModel.TYPE_CONTENT + "'))]"; - - /** Shallow search for all folder */ - private static final String XPATH_QUERY_SHALLOW_FOLDERS = - "./*" + - "[not (subtypeOf('" + ContentModel.TYPE_SYSTEM_FOLDER + "'))" + - " and (subtypeOf('" + ContentModel.TYPE_FOLDER + "'))]"; - - /** Shallow search for all files and folders */ + /** Shallow search for files and folders with a name pattern */ private static final String XPATH_QUERY_SHALLOW_ALL = "./*" + "[like(@cm:name, $cm:name, false)" + " and not (subtypeOf('" + ContentModel.TYPE_SYSTEM_FOLDER + "'))" + " and (subtypeOf('" + ContentModel.TYPE_FOLDER + "') or subtypeOf('" + ContentModel.TYPE_CONTENT + "'))]"; + /** Shallow search for all files and folders */ + private static final String LUCENE_QUERY_SHALLOW_ALL = + "+PARENT:\"${cm:parent}\"" + + "-TYPE:\"" + ContentModel.TYPE_SYSTEM_FOLDER + "\" "; + + /** Shallow search for all files and folders */ + private static final String LUCENE_QUERY_SHALLOW_FOLDERS = + "+PARENT:\"${cm:parent}\"" + + "-TYPE:\"" + ContentModel.TYPE_SYSTEM_FOLDER + "\" " + + "+TYPE:\"" + ContentModel.TYPE_FOLDER + "\" "; + + /** Shallow search for all files and folders */ + private static final String LUCENE_QUERY_SHALLOW_FILES = + "+PARENT:\"${cm:parent}\"" + + "-TYPE:\"" + ContentModel.TYPE_SYSTEM_FOLDER + "\" " + + "+TYPE:\"" + ContentModel.TYPE_CONTENT + "\" "; + /** Deep search for files and folders with a name pattern */ private static final String XPATH_QUERY_DEEP_ALL = ".//*" + @@ -85,8 +95,8 @@ public class FileFolderServiceImpl implements FileFolderService " and (subtypeOf('" + ContentModel.TYPE_FOLDER + "') or subtypeOf('" + ContentModel.TYPE_CONTENT + "'))]"; /** empty parameters */ - private static final QueryParameterDefinition[] PARAMS_EMPTY = new QueryParameterDefinition[0]; private static final QueryParameterDefinition[] PARAMS_ANY_NAME = new QueryParameterDefinition[1]; + private static final QName PARAM_QNAME_PARENT = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "parent"); private static Log logger = LogFactory.getLog(FileFolderServiceImpl.class); @@ -100,6 +110,7 @@ public class FileFolderServiceImpl implements FileFolderService // TODO: Replace this with a more formal means of identifying "system" folders (i.e. aspect or UUID) private List systemPaths; + private DataTypeDefinition dataTypeNodeRef; /** * Default constructor @@ -157,6 +168,7 @@ public class FileFolderServiceImpl implements FileFolderService dictionaryService.getDataType(DataTypeDefinition.TEXT), true, "%"); + dataTypeNodeRef = dictionaryService.getDataType(DataTypeDefinition.NODE_REF); } /** @@ -194,22 +206,6 @@ public class FileFolderServiceImpl implements FileFolderService return fileInfo; } - /** - * Ensure that a file or folder with the given name does not already exist - * - * @throws FileExistsException if a same-named file or folder already exists - */ - private void checkExists(NodeRef parentFolderRef, String name) - throws FileExistsException - { - // check for existing file or folder - List existingFileInfos = this.search(parentFolderRef, name, true, true, false); - if (existingFileInfos.size() > 0) - { - throw new FileExistsException(existingFileInfos.get(0)); - } - } - /** * Exception when the type is not a valid File or Folder type * @@ -258,18 +254,10 @@ public class FileFolderServiceImpl implements FileFolderService } } - /** - * TODO: Use Lucene search to get file attributes without having to visit the node service - */ public List list(NodeRef contextNodeRef) { // execute the query - List nodeRefs = searchService.selectNodes( - contextNodeRef, - XPATH_QUERY_SHALLOW_ALL, - PARAMS_ANY_NAME, - namespaceService, - false); + List nodeRefs = luceneSearch(contextNodeRef, true, true); // convert the noderefs List results = toFileInfo(nodeRefs); // done @@ -282,18 +270,10 @@ public class FileFolderServiceImpl implements FileFolderService return results; } - /** - * TODO: Use Lucene search to get file attributes without having to visit the node service - */ public List listFiles(NodeRef contextNodeRef) { // execute the query - List nodeRefs = searchService.selectNodes( - contextNodeRef, - XPATH_QUERY_SHALLOW_FILES, - PARAMS_EMPTY, - namespaceService, - false); + List nodeRefs = luceneSearch(contextNodeRef, false, true); // convert the noderefs List results = toFileInfo(nodeRefs); // done @@ -306,18 +286,10 @@ public class FileFolderServiceImpl implements FileFolderService return results; } - /** - * TODO: Use Lucene search to get file attributes without having to visit the node service - */ public List listFolders(NodeRef contextNodeRef) { // execute the query - List nodeRefs = searchService.selectNodes( - contextNodeRef, - XPATH_QUERY_SHALLOW_FOLDERS, - PARAMS_EMPTY, - namespaceService, - false); + List nodeRefs = luceneSearch(contextNodeRef, true, false); // convert the noderefs List results = toFileInfo(nodeRefs); // done @@ -329,6 +301,20 @@ public class FileFolderServiceImpl implements FileFolderService } return results; } + + public NodeRef searchSimple(NodeRef contextNodeRef, String name) + { + NodeRef childNodeRef = nodeService.getChildByName(contextNodeRef, ContentModel.ASSOC_CONTAINS, name); + if (logger.isDebugEnabled()) + { + logger.debug( + "Simple name search results: \n" + + " parent: " + contextNodeRef + "\n" + + " name: " + name + "\n" + + " result: " + childNodeRef); + } + return childNodeRef; + } /** * @see #search(NodeRef, String, boolean, boolean, boolean) @@ -338,6 +324,7 @@ public class FileFolderServiceImpl implements FileFolderService return search(contextNodeRef, namePattern, true, true, includeSubFolders); } + private static final String LUCENE_MULTI_CHAR_WILDCARD = "*"; /** * Full search with all options */ @@ -348,50 +335,9 @@ public class FileFolderServiceImpl implements FileFolderService boolean folderSearch, boolean includeSubFolders) { - // shortcut if the search is requesting nothing - if (!fileSearch && !folderSearch) - { - return Collections.emptyList(); - } + // get the raw nodeRefs + List nodeRefs = searchInternal(contextNodeRef, namePattern, fileSearch, folderSearch, includeSubFolders); - // if the name pattern is null, then we use the ANY pattern - QueryParameterDefinition[] params = null; - if (namePattern != null) - { - // the interface specifies the Lucene syntax, so perform a conversion - namePattern = SearchLanguageConversion.convert( - SearchLanguageConversion.DEF_LUCENE, - SearchLanguageConversion.DEF_XPATH_LIKE, - namePattern); - - params = new QueryParameterDefinition[1]; - params[0] = new QueryParameterDefImpl( - ContentModel.PROP_NAME, - dictionaryService.getDataType(DataTypeDefinition.TEXT), - true, - namePattern); - } - else - { - params = PARAMS_ANY_NAME; - } - // determine the correct query to use - String query = null; - if (includeSubFolders) - { - query = XPATH_QUERY_DEEP_ALL; - } - else - { - query = XPATH_QUERY_SHALLOW_ALL; - } - // execute the query - List nodeRefs = searchService.selectNodes( - contextNodeRef, - query, - params, - namespaceService, - false); List results = toFileInfo(nodeRefs); // eliminate unwanted files/folders Iterator iterator = results.iterator(); @@ -420,6 +366,133 @@ public class FileFolderServiceImpl implements FileFolderService } return results; } + + /** + * Performs a full search, but doesn't translate the node references into + * file info objects. This allows {@link #checkExists(NodeRef, String)} to + * bypass the retrieval of node properties. + */ + private List searchInternal( + NodeRef contextNodeRef, + String namePattern, + boolean fileSearch, + boolean folderSearch, + boolean includeSubFolders) + { + // shortcut if the search is requesting nothing + if (!fileSearch && !folderSearch) + { + return Collections.emptyList(); + } + + if (namePattern == null) + { + namePattern = LUCENE_MULTI_CHAR_WILDCARD; // default to wildcard + } + // now check if we can use Lucene to handle this query + boolean useLucene = false; + boolean anyName = namePattern.equals(LUCENE_MULTI_CHAR_WILDCARD); + if (!includeSubFolders && anyName) + { + // Lucene only handles any name or exact name + useLucene = true; + } + + List nodeRefs = null; + if (!useLucene) // go with the XPath queries + { + // if the name pattern is null, then we use the ANY pattern + QueryParameterDefinition[] params = null; + if (namePattern != null) + { + // the interface specifies the Lucene syntax, so perform a conversion + namePattern = SearchLanguageConversion.convert( + SearchLanguageConversion.DEF_LUCENE, + SearchLanguageConversion.DEF_XPATH_LIKE, + namePattern); + + params = new QueryParameterDefinition[1]; + params[0] = new QueryParameterDefImpl( + ContentModel.PROP_NAME, + dictionaryService.getDataType(DataTypeDefinition.TEXT), + true, + namePattern); + } + else + { + params = PARAMS_ANY_NAME; + } + // determine the correct query to use + String query = null; + if (includeSubFolders) + { + query = XPATH_QUERY_DEEP_ALL; + } + else + { + query = XPATH_QUERY_SHALLOW_ALL; + } + // execute the query + nodeRefs = searchService.selectNodes( + contextNodeRef, + query, + params, + namespaceService, + false); + } + else // go with Lucene queries + { + nodeRefs = luceneSearch(contextNodeRef, folderSearch, fileSearch); + } + // done + return nodeRefs; + } + + private List luceneSearch(NodeRef contextNodeRef, boolean folders, boolean files) + { + SearchParameters params = new SearchParameters(); + params.setLanguage(SearchService.LANGUAGE_LUCENE); + params.addStore(contextNodeRef.getStoreRef()); + // set the parent parameter + QueryParameterDefinition parentParamDef = new QueryParameterDefImpl( + PARAM_QNAME_PARENT, + dataTypeNodeRef, + true, + contextNodeRef.toString()); + params.addQueryParameterDefinition(parentParamDef); + if (folders && files) // search for both files and folders + { + params.setQuery(LUCENE_QUERY_SHALLOW_ALL); + } + else if (folders) // search for folders only + { + params.setQuery(LUCENE_QUERY_SHALLOW_FOLDERS); + } + else if (files) // search for files only + { + params.setQuery(LUCENE_QUERY_SHALLOW_FILES); + } + else + { + throw new IllegalArgumentException("Must search for either files or folders or both"); + } + ResultSet rs = searchService.query(params); + int length = rs.length(); + List nodeRefs = new ArrayList(length); + try + { + for (ResultSetRow row : rs) + { + nodeRefs.add(row.getNodeRef()); + } + } + finally + { + rs.close(); + } + // done + return nodeRefs; + } /** * @see #move(NodeRef, NodeRef, String) @@ -467,10 +540,9 @@ public class FileFolderServiceImpl implements FileFolderService targetParentRef = assocRef.getParentRef(); } - boolean checkExists = true; + // there is nothing to do if both the name and parent folder haven't changed if (targetParentRef.equals(assocRef.getParentRef())) { - // there is nothing to do if both the name and parent folder haven't changed if (newName.equals(beforeFileInfo.getName())) { if (logger.isDebugEnabled()) @@ -484,18 +556,9 @@ public class FileFolderServiceImpl implements FileFolderService } else if (newName.equalsIgnoreCase(beforeFileInfo.getName())) { - // name has only changed case so don't bother with exists check - checkExists = false; } } - // check for existing file or folder (if name has changed) - if (checkExists) - { - checkExists(targetParentRef, newName); - } - - QName qname = QName.createQName( NamespaceService.CONTENT_MODEL_1_0_URI, QName.createValidLocalName(newName)); @@ -536,8 +599,15 @@ public class FileFolderServiceImpl implements FileFolderService String currentName = (String)nodeService.getProperty(targetNodeRef, ContentModel.PROP_NAME); if (currentName.equals(newName) == false) { - // changed the name property - nodeService.setProperty(targetNodeRef, ContentModel.PROP_NAME, newName); + try + { + // changed the name property + nodeService.setProperty(targetNodeRef, ContentModel.PROP_NAME, newName); + } + catch (DuplicateChildNodeNameException e) + { + throw new FileExistsException(targetParentRef, newName); + } } // get the details after the operation @@ -581,9 +651,6 @@ public class FileFolderServiceImpl implements FileFolderService throw new AlfrescoRuntimeException("The type is not supported by this service: " + typeQName); } - // check for existing file or folder - checkExists(parentNodeRef, name); - // set up initial properties Map properties = new HashMap(11); properties.put(ContentModel.PROP_NAME, (Serializable) name); @@ -599,12 +666,21 @@ public class FileFolderServiceImpl implements FileFolderService QName qname = QName.createQName( NamespaceService.CONTENT_MODEL_1_0_URI, QName.createValidLocalName(name)); - ChildAssociationRef assocRef = nodeService.createNode( - parentNodeRef, - ContentModel.ASSOC_CONTAINS, - qname, - typeQName, - properties); + ChildAssociationRef assocRef = null; + try + { + assocRef = nodeService.createNode( + parentNodeRef, + ContentModel.ASSOC_CONTAINS, + qname, + typeQName, + properties); + } + catch (DuplicateChildNodeNameException e) + { + throw new FileExistsException(parentNodeRef, name); + } + NodeRef nodeRef = assocRef.getChildRef(); FileInfo fileInfo = toFileInfo(nodeRef); // done @@ -639,31 +715,25 @@ public class FileFolderServiceImpl implements FileFolderService NodeRef currentParentRef = parentNodeRef; // just loop and create if necessary - FileInfo lastFileInfo = null; for (String pathElement : pathElements) { - try + // does it exist? + NodeRef nodeRef = searchSimple(currentParentRef, pathElement); + if (nodeRef == null) { // not present - make it FileInfo createdFileInfo = create(currentParentRef, pathElement, folderTypeQName); currentParentRef = createdFileInfo.getNodeRef(); - lastFileInfo = createdFileInfo; } - catch (FileExistsException e) + else { - // it exists - just get it - List fileInfos = search(currentParentRef, pathElement, false, true, false); - if (fileInfos.size() == 0) - { - // ? It must have been removed - throw new AlfrescoRuntimeException("Path element has just been removed: " + pathElement); - } - currentParentRef = fileInfos.get(0).getNodeRef(); - lastFileInfo = fileInfos.get(0); + // it exists + currentParentRef = nodeRef; } } // done - return lastFileInfo; + FileInfo fileInfo = toFileInfo(currentParentRef); + return fileInfo; } public List getNamePath(NodeRef rootNodeRef, NodeRef nodeRef) throws FileNotFoundException diff --git a/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImplTest.java b/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImplTest.java index dc9bb09cbf..d42eccd354 100644 --- a/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImplTest.java +++ b/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImplTest.java @@ -66,7 +66,8 @@ public class FileFolderServiceImplTest extends TestCase private static final String NAME_L1_FILE_A = "L1- File A"; private static final String NAME_L1_FILE_B = "L1- File B"; private static final String NAME_L1_FILE_C = "L1- File C (%_)"; - private static final String NAME_DUPLICATE = "DUPLICATE"; + private static final String NAME_CHECK_FILE = "CHECK_FILE"; + private static final String NAME_CHECK_FOLDER = "CHECK_FOLDER"; private static final ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); @@ -258,11 +259,11 @@ public class FileFolderServiceImplTest extends TestCase */ public void testGetByName() throws Exception { - FileInfo fileInfo = getByName(NAME_DUPLICATE, true); + FileInfo fileInfo = getByName(NAME_CHECK_FOLDER, true); assertNotNull(fileInfo); assertTrue(fileInfo.isFolder()); - fileInfo = getByName(NAME_DUPLICATE, false); + fileInfo = getByName(NAME_CHECK_FILE, false); assertNotNull(fileInfo); assertFalse(fileInfo.isFolder()); } @@ -308,6 +309,10 @@ public class FileFolderServiceImplTest extends TestCase // make sure that it is an immediate child of the root List checkFileInfos = fileFolderService.search(workingRootNodeRef, NAME_L1_FOLDER_A, false); assertEquals("Folder not moved to root", 1, checkFileInfos.size()); + // rename properly + FileInfo checkFileInfo = fileFolderService.move(folderToMoveRef, null, "new name"); + checkFileInfos = fileFolderService.search(workingRootNodeRef, checkFileInfo.getName(), false); + assertEquals("Folder not renamed in root", 1, checkFileInfos.size()); // attempt illegal rename (existing) try { @@ -318,10 +323,6 @@ public class FileFolderServiceImplTest extends TestCase { // expected } - // rename properly - FileInfo checkFileInfo = fileFolderService.move(folderToMoveRef, null, "new name"); - checkFileInfos = fileFolderService.search(workingRootNodeRef, checkFileInfo.getName(), false); - assertEquals("Folder not renamed in root", 1, checkFileInfos.size()); } public void testCopy() throws Exception @@ -417,19 +418,20 @@ public class FileFolderServiceImplTest extends TestCase { // create a completely new path below the root List namePath = new ArrayList(4); - namePath.add("A"); - namePath.add("B"); - namePath.add("C"); - namePath.add("D"); + namePath.add("AAA"); + namePath.add("BBB"); + namePath.add("CCC"); + namePath.add("DDD"); FileInfo lastFileInfo = fileFolderService.makeFolders(rootNodeRef, namePath, ContentModel.TYPE_FOLDER); assertNotNull("First makeFolder failed", lastFileInfo); // check that a repeat works + FileInfo lastFileInfoAgain = fileFolderService.makeFolders(rootNodeRef, namePath, ContentModel.TYPE_FOLDER); assertNotNull("Repeat makeFolders failed", lastFileInfoAgain); assertEquals("Repeat created new leaf", lastFileInfo.getNodeRef(), lastFileInfoAgain.getNodeRef()); // check that it worked - List checkInfos = fileFolderService.search(rootNodeRef, "D", false, true, true); + List checkInfos = fileFolderService.search(rootNodeRef, "DDD", false, true, true); assertEquals("Expected to find a result", 1, checkInfos.size()); // get the path List checkPathInfos = fileFolderService.getNamePath(rootNodeRef, checkInfos.get(0).getNodeRef()); @@ -442,6 +444,27 @@ public class FileFolderServiceImplTest extends TestCase } } + /** + * Lucene only indexes terms that are 3 characters or more + */ + public void testMakeFoldersShortNames() throws Exception + { + // create a completely new path below the root + List namePath = new ArrayList(4); + namePath.add("A"); + namePath.add("B"); + namePath.add("C"); + namePath.add("D"); + + FileInfo lastFileInfo = fileFolderService.makeFolders(rootNodeRef, namePath, ContentModel.TYPE_FOLDER); + assertNotNull("First makeFolder failed", lastFileInfo); + // check that a repeat works + + FileInfo lastFileInfoAgain = fileFolderService.makeFolders(rootNodeRef, namePath, ContentModel.TYPE_FOLDER); + assertNotNull("Repeat makeFolders failed", lastFileInfoAgain); + assertEquals("Repeat created new leaf", lastFileInfo.getNodeRef(), lastFileInfoAgain.getNodeRef()); + } + public void testGetNamePath() throws Exception { FileInfo fileInfo = getByName(NAME_L1_FILE_A, false); @@ -473,6 +496,22 @@ public class FileFolderServiceImplTest extends TestCase } } + public void testSearchSimple() throws Exception + { + FileInfo folderInfo = getByName(NAME_L0_FOLDER_A, true); + assertNotNull(folderInfo); + NodeRef folderNodeRef = folderInfo.getNodeRef(); + // search for a file that is not there + NodeRef phantomNodeRef = fileFolderService.searchSimple(folderNodeRef, "aaaaaaa"); + assertNull("Found non-existent node by name", phantomNodeRef); + // search for a file that is there + NodeRef fileNodeRef = fileFolderService.searchSimple(folderNodeRef, NAME_L1_FILE_A); + assertNotNull("Didn't find file", fileNodeRef); + // double check + FileInfo checkInfo = getByName(NAME_L1_FILE_A, false); + assertEquals("Incorrect node found", checkInfo.getNodeRef(), fileNodeRef); + } + public void testResolveNamePath() throws Exception { FileInfo fileInfo = getByName(NAME_L1_FILE_A, false); @@ -524,4 +563,20 @@ public class FileFolderServiceImplTest extends TestCase String checkContent = reader.getContentString(); assertEquals("Content mismatch", content, checkContent); } + + public void testLongFileNames() throws Exception + { + String fileName = + "12345678901234567890123456789012345678901234567890" + + "12345678901234567890123456789012345678901234567890" + + "12345678901234567890123456789012345678901234567890" + + "12345678901234567890123456789012345678901234567890" + + "12345678901234567890123456789012345678901234567890" + + "12345678901234567890123456789012345678901234567890"; + FileInfo fileInfo = fileFolderService.create(workingRootNodeRef, fileName, ContentModel.TYPE_CONTENT); + // see if we can get it again + NodeRef fileNodeRef = fileFolderService.searchSimple(workingRootNodeRef, fileName); + assertNotNull("Long filename not found", fileNodeRef); + assertEquals(fileInfo.getNodeRef(), fileNodeRef); + } } diff --git a/source/java/org/alfresco/repo/node/AbstractNodeServiceImpl.java b/source/java/org/alfresco/repo/node/AbstractNodeServiceImpl.java index 7f9b75741f..960b763527 100644 --- a/source/java/org/alfresco/repo/node/AbstractNodeServiceImpl.java +++ b/source/java/org/alfresco/repo/node/AbstractNodeServiceImpl.java @@ -42,6 +42,7 @@ import org.alfresco.repo.node.NodeServicePolicies.OnCreateStorePolicy; import org.alfresco.repo.node.NodeServicePolicies.OnDeleteAssociationPolicy; import org.alfresco.repo.node.NodeServicePolicies.OnDeleteChildAssociationPolicy; import org.alfresco.repo.node.NodeServicePolicies.OnDeleteNodePolicy; +import org.alfresco.repo.node.NodeServicePolicies.OnMoveNodePolicy; import org.alfresco.repo.node.NodeServicePolicies.OnRemoveAspectPolicy; import org.alfresco.repo.node.NodeServicePolicies.OnUpdateNodePolicy; import org.alfresco.repo.node.NodeServicePolicies.OnUpdatePropertiesPolicy; @@ -99,6 +100,7 @@ public abstract class AbstractNodeServiceImpl implements NodeService private ClassPolicyDelegate onCreateStoreDelegate; private ClassPolicyDelegate beforeCreateNodeDelegate; private ClassPolicyDelegate onCreateNodeDelegate; + private ClassPolicyDelegate onMoveNodeDelegate; private ClassPolicyDelegate beforeUpdateNodeDelegate; private ClassPolicyDelegate onUpdateNodeDelegate; private ClassPolicyDelegate onUpdatePropertiesDelegate; @@ -169,6 +171,7 @@ public abstract class AbstractNodeServiceImpl implements NodeService onCreateStoreDelegate = policyComponent.registerClassPolicy(NodeServicePolicies.OnCreateStorePolicy.class); beforeCreateNodeDelegate = policyComponent.registerClassPolicy(NodeServicePolicies.BeforeCreateNodePolicy.class); onCreateNodeDelegate = policyComponent.registerClassPolicy(NodeServicePolicies.OnCreateNodePolicy.class); + onMoveNodeDelegate = policyComponent.registerClassPolicy(NodeServicePolicies.OnMoveNodePolicy.class); beforeUpdateNodeDelegate = policyComponent.registerClassPolicy(NodeServicePolicies.BeforeUpdateNodePolicy.class); onUpdateNodeDelegate = policyComponent.registerClassPolicy(NodeServicePolicies.OnUpdateNodePolicy.class); onUpdatePropertiesDelegate = policyComponent.registerClassPolicy(NodeServicePolicies.OnUpdatePropertiesPolicy.class); @@ -235,6 +238,19 @@ public abstract class AbstractNodeServiceImpl implements NodeService policy.onCreateNode(childAssocRef); } + /** + * @see NodeServicePolicies.OnMoveNodePolicy#onMoveNode(ChildAssociationRef, ChildAssociationRef) + */ + protected void invokeOnMoveNode(ChildAssociationRef oldChildAssocRef, ChildAssociationRef newChildAssocRef) + { + NodeRef childNodeRef = newChildAssocRef.getChildRef(); + // get qnames to invoke against + Set qnames = getTypeAndAspectQNames(childNodeRef); + // execute policy for node type and aspects + NodeServicePolicies.OnMoveNodePolicy policy = onMoveNodeDelegate.get(childNodeRef, qnames); + policy.onMoveNode(oldChildAssocRef, newChildAssocRef); + } + /** * @see NodeServicePolicies.BeforeUpdateNodePolicy#beforeUpdateNode(NodeRef) */ @@ -326,7 +342,7 @@ public abstract class AbstractNodeServiceImpl implements NodeService /** * @see NodeServicePolicies.OnDeleteNodePolicy#onDeleteNode(ChildAssociationRef) */ - protected void invokeOnDeleteNode(ChildAssociationRef childAssocRef, QName childNodeTypeQName, Set childAspectQnames) + protected void invokeOnDeleteNode(ChildAssociationRef childAssocRef, QName childNodeTypeQName, Set childAspectQnames, boolean isArchivedNode) { // get qnames to invoke against Set qnames = new HashSet(childAspectQnames.size() + 1); @@ -335,7 +351,7 @@ public abstract class AbstractNodeServiceImpl implements NodeService // execute policy for node type and aspects NodeServicePolicies.OnDeleteNodePolicy policy = onDeleteNodeDelegate.get(childAssocRef.getChildRef(), qnames); - policy.onDeleteNode(childAssocRef); + policy.onDeleteNode(childAssocRef, isArchivedNode); } /** diff --git a/source/java/org/alfresco/repo/node/BaseNodeServiceTest.java b/source/java/org/alfresco/repo/node/BaseNodeServiceTest.java index 441fbbfd4a..0a5709f410 100644 --- a/source/java/org/alfresco/repo/node/BaseNodeServiceTest.java +++ b/source/java/org/alfresco/repo/node/BaseNodeServiceTest.java @@ -32,6 +32,7 @@ import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.dictionary.DictionaryComponent; import org.alfresco.repo.dictionary.DictionaryDAO; import org.alfresco.repo.dictionary.M2Model; +import org.alfresco.repo.domain.hibernate.ChildAssocImpl; import org.alfresco.repo.domain.hibernate.NodeImpl; import org.alfresco.repo.node.db.NodeDaoService; import org.alfresco.repo.policy.JavaBehaviour; @@ -47,6 +48,7 @@ import org.alfresco.service.cmr.repository.AssociationRef; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.CyclicChildRelationshipException; +import org.alfresco.service.cmr.repository.DuplicateChildNodeNameException; import org.alfresco.service.cmr.repository.InvalidNodeRefException; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; @@ -107,6 +109,7 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest public static final QName PROP_QNAME_PROP1 = QName.createQName(NAMESPACE, "prop1"); public static final QName PROP_QNAME_PROP2 = QName.createQName(NAMESPACE, "prop2"); public static final QName ASSOC_TYPE_QNAME_TEST_CHILDREN = ContentModel.ASSOC_CHILDREN; + public static final QName ASSOC_TYPE_QNAME_TEST_CONTAINS = ContentModel.ASSOC_CONTAINS; public static final QName ASSOC_TYPE_QNAME_TEST_NEXT = QName.createQName(NAMESPACE, "next"); public static final QName TYPE_QNAME_TEST_MULTIPLE_TESTER = QName.createQName(NAMESPACE, "multiple-tester"); @@ -679,7 +682,7 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest nodeService.addAspect(nodeRef, ASPECT_QNAME_TEST_TITLED, null); } - public void onDeleteNode(ChildAssociationRef childAssocRef) + public void onDeleteNode(ChildAssociationRef childAssocRef, boolean isArchivedNode) { // add the child to the list deletedNodeRefs.add(childAssocRef.getChildRef()); @@ -730,9 +733,10 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest private int countChildrenOfNode(NodeRef nodeRef) { String query = - "select node.childAssocs" + + "select childAssoc" + " from " + - NodeImpl.class.getName() + " node" + + ChildAssocImpl.class.getName() + " childAssoc" + + " join childAssoc.parent node" + " where node.uuid = ? and node.store.key.protocol = ? and node.store.key.identifier = ?"; Session session = getSession(); List results = session.createQuery(query) @@ -761,17 +765,24 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest public void testAddChild() throws Exception { - NodeRef childNodeRef = nodeService.createNode( + NodeRef childNodeRef1 = nodeService.createNode( rootNodeRef, ASSOC_TYPE_QNAME_TEST_CHILDREN, - QName.createQName("pathA"), + QName.createQName("C1"), ContentModel.TYPE_CONTAINER).getChildRef(); - int countBefore = countChildrenOfNode(rootNodeRef); - assertEquals("Root children count incorrect", 1, countBefore); + int count = countChildrenOfNode(rootNodeRef); + assertEquals("Root children count incorrect", 1, count); + NodeRef childNodeRef2 = nodeService.createNode( + childNodeRef1, + ASSOC_TYPE_QNAME_TEST_CHILDREN, + QName.createQName("C2"), + ContentModel.TYPE_CONTAINER).getChildRef(); + count = countChildrenOfNode(rootNodeRef); + assertEquals("Root children count incorrect", 1, count); // associate the two nodes nodeService.addChild( rootNodeRef, - childNodeRef, + childNodeRef2, ASSOC_TYPE_QNAME_TEST_CHILDREN, QName.createQName("pathB")); // there should now be 2 child assocs on the root @@ -782,7 +793,7 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest try { nodeService.addChild( - childNodeRef, + childNodeRef1, rootNodeRef, ASSOC_TYPE_QNAME_TEST_CHILDREN, QName.createQName("backToRoot")); @@ -807,10 +818,6 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest QName.createQName("pathA"), ContentModel.TYPE_CONTAINER); NodeRef childARef = pathARef.getChildRef(); - ChildAssociationRef pathBRef = nodeService.addChild( - parentRef, childARef, ASSOC_TYPE_QNAME_TEST_CHILDREN, QName.createQName("pathB")); - ChildAssociationRef pathCRef = nodeService.addChild( - parentRef, childARef, ASSOC_TYPE_QNAME_TEST_CHILDREN, QName.createQName("pathC")); AssociationRef pathDRef = nodeService.createAssociation( parentRef, childARef, ASSOC_TYPE_QNAME_TEST_NEXT); // remove the child - this must cascade @@ -826,24 +833,6 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest 0, nodeService.getTargetAssocs(parentRef, RegexQNamePattern.MATCH_ALL).size()); } - public void testAddAndRemoveChild() throws Exception - { - ChildAssociationRef pathARef = nodeService.createNode( - rootNodeRef, - ASSOC_TYPE_QNAME_TEST_CHILDREN, - QName.createQName("pathA"), - ContentModel.TYPE_CONTAINER); - NodeRef childRef = pathARef.getChildRef(); - // make a duplication, but non-primary, child associaton - nodeService.addChild( - rootNodeRef, - pathARef.getChildRef(), - pathARef.getTypeQName(), - pathARef.getQName()); - // now remove the association - it will cascade to the child - nodeService.removeChild(rootNodeRef, childRef); - } - public enum TestEnum { TEST_ONE, @@ -1153,6 +1142,17 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest RegexQNamePattern.MATCH_ALL); } + public static class MovePolicyTester implements NodeServicePolicies.OnMoveNodePolicy + { + public List policyAssocRefs = new ArrayList(2); + public void onMoveNode(ChildAssociationRef oldChildAssocRef, ChildAssociationRef newChildAssocRef) + { + policyAssocRefs.add(oldChildAssocRef); + policyAssocRefs.add(newChildAssocRef); + } + }; + + public void testMoveNode() throws Exception { Map assocRefs = buildNodeGraph(); @@ -1163,12 +1163,24 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest NodeRef n5Ref = n5pn7Ref.getParentRef(); NodeRef n6Ref = n6pn8Ref.getParentRef(); NodeRef n8Ref = n6pn8Ref.getChildRef(); + + MovePolicyTester policy = new MovePolicyTester(); + // bind to listen to the deletion of a node + policyComponent.bindClassBehaviour( + QName.createQName(NamespaceService.ALFRESCO_URI, "onMoveNode"), + policy, + new JavaBehaviour(policy, "onMoveNode")); + // move n8 to n5 ChildAssociationRef assocRef = nodeService.moveNode( n8Ref, n5Ref, ASSOC_TYPE_QNAME_TEST_CHILDREN, QName.createQName(BaseNodeServiceTest.NAMESPACE, "n5_p_n8")); + + // check that the move policy was fired + assertEquals("Move policy not fired", 2, policy.policyAssocRefs.size()); + // check that n6 is no longer the parent List n6ChildRefs = nodeService.getChildAssocs( n6Ref, @@ -1498,4 +1510,137 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest " total nodes: " + totalNodes + "\n" + " total assocs: " + totalAssocs); } + + /** + * Check that the duplicate child name is detected and thrown correctly + */ + public void testDuplicateCatch() throws Exception + { + NodeRef parentRef = nodeService.createNode( + rootNodeRef, + ASSOC_TYPE_QNAME_TEST_CHILDREN, + QName.createQName("parent_child"), + ContentModel.TYPE_CONTAINER).getChildRef(); + ChildAssociationRef pathARef = nodeService.createNode( + parentRef, + ASSOC_TYPE_QNAME_TEST_CONTAINS, + QName.createQName("pathA"), + ContentModel.TYPE_CONTENT); + // no issue with this + ChildAssociationRef pathBRef = nodeService.createNode( + parentRef, + ASSOC_TYPE_QNAME_TEST_CONTAINS, + QName.createQName("pathB"), + ContentModel.TYPE_CONTENT); + AlfrescoTransactionSupport.flush(); + // there should be no issue with a duplicate association names any more + ChildAssociationRef pathBDuplicateRef = nodeService.createNode( + parentRef, + ASSOC_TYPE_QNAME_TEST_CONTAINS, + QName.createQName("pathB"), + ContentModel.TYPE_CONTENT); + AlfrescoTransactionSupport.flush(); + // Now create nodes with duplicate cm:name properties + Map props = new HashMap(5); + props.put(ContentModel.PROP_NAME, "ABC"); + ChildAssociationRef pathAbcRef = nodeService.createNode( + parentRef, + ASSOC_TYPE_QNAME_TEST_CONTAINS, + QName.createQName("ABC"), + ContentModel.TYPE_CONTENT, + props); + AlfrescoTransactionSupport.flush(); + try + { + // now check that the duplicate is detected with attention to being case-insensitive + props.put(ContentModel.PROP_NAME, "abc"); + ChildAssociationRef pathAbcDuplicateRef = nodeService.createNode( + parentRef, + ASSOC_TYPE_QNAME_TEST_CONTAINS, + QName.createQName("ABC-duplicate"), + ContentModel.TYPE_CONTENT, + props); + fail("Failed to throw duplicate child name exception"); + } + catch (DuplicateChildNodeNameException e) + { + // expected + } + } + + /** + * Checks that the unique constraint doesn't break delete and create within the same + * transaction. + */ + public void testDeleteAndAddSameName() throws Exception + { + NodeRef parentRef = nodeService.createNode( + rootNodeRef, + ASSOC_TYPE_QNAME_TEST_CHILDREN, + QName.createQName("parent_child"), + ContentModel.TYPE_CONTAINER).getChildRef(); + // create node ABC + Map props = new HashMap(5); + props.put(ContentModel.PROP_NAME, "ABC"); + ChildAssociationRef pathAbcRef = nodeService.createNode( + parentRef, + ASSOC_TYPE_QNAME_TEST_CONTAINS, + QName.createQName("ABC"), + ContentModel.TYPE_CONTENT, + props); + NodeRef abcRef = pathAbcRef.getChildRef(); + AlfrescoTransactionSupport.flush(); + // delete ABC + nodeService.deleteNode(abcRef); + // create it again + pathAbcRef = nodeService.createNode( + parentRef, + ASSOC_TYPE_QNAME_TEST_CONTAINS, + QName.createQName("ABC"), + ContentModel.TYPE_CONTENT, + props); + // there should not be any failure when doing this in the same transaction + } + + public void testGetByName() throws Exception + { + NodeRef parentRef = nodeService.createNode( + rootNodeRef, + ASSOC_TYPE_QNAME_TEST_CHILDREN, + QName.createQName("parent_child"), + ContentModel.TYPE_CONTAINER).getChildRef(); + // create node ABC + Map props = new HashMap(5); + props.put(ContentModel.PROP_NAME, "ABC"); + ChildAssociationRef pathAbcRef = nodeService.createNode( + parentRef, + ASSOC_TYPE_QNAME_TEST_CONTAINS, + QName.createQName("ABC"), + ContentModel.TYPE_CONTENT, + props); + NodeRef abcRef = pathAbcRef.getChildRef(); + // create node DEF + props.put(ContentModel.PROP_NAME, "DEF"); + ChildAssociationRef pathDefRef = nodeService.createNode( + abcRef, + ASSOC_TYPE_QNAME_TEST_CONTAINS, + QName.createQName("DEF"), + ContentModel.TYPE_CONTENT, + props); + NodeRef defRef = pathDefRef.getChildRef(); + + // now browse down using the node service + NodeRef checkParentRef = nodeService.getChildByName(rootNodeRef, ASSOC_TYPE_QNAME_TEST_CHILDREN, parentRef.getId()); + assertNotNull("First level, non-named node not found", checkParentRef); + assertEquals(parentRef, checkParentRef); + NodeRef checkAbcRef = nodeService.getChildByName(checkParentRef, ASSOC_TYPE_QNAME_TEST_CONTAINS, "abc"); + assertNotNull("Second level, named node 'ABC' not found", checkAbcRef); + assertEquals(abcRef, checkAbcRef); + NodeRef checkDefRef = nodeService.getChildByName(checkAbcRef, ASSOC_TYPE_QNAME_TEST_CONTAINS, "def"); + assertNotNull("Third level, named node 'DEF' not found", checkDefRef); + assertEquals(defRef, checkDefRef); + // check that we get null where not present + NodeRef checkHijRef = nodeService.getChildByName(checkAbcRef, ASSOC_TYPE_QNAME_TEST_CONTAINS, "hij"); + assertNull("Third level, named node 'HIJ' should not have been found", checkHijRef); + } } diff --git a/source/java/org/alfresco/repo/node/NodeServicePolicies.java b/source/java/org/alfresco/repo/node/NodeServicePolicies.java index 3745bae39d..288e24606b 100644 --- a/source/java/org/alfresco/repo/node/NodeServicePolicies.java +++ b/source/java/org/alfresco/repo/node/NodeServicePolicies.java @@ -82,6 +82,17 @@ public interface NodeServicePolicies public void onCreateNode(ChildAssociationRef childAssocRef); } + public interface OnMoveNodePolicy extends ClassPolicy + { + /** + * Called when a node has been moved. + * + * @param oldChildAssocRef the child association reference prior to the move + * @param newChildAssocRef the child association reference after the move + */ + public void onMoveNode(ChildAssociationRef oldChildAssocRef, ChildAssociationRef newChildAssocRef); + } + public interface BeforeUpdateNodePolicy extends ClassPolicy { /** @@ -140,9 +151,10 @@ public interface NodeServicePolicies * which has been deleted and cannot be used to retrieve node or associaton * information from any of the services. * - * @param childAssocRef the primary parent-child association of the deleted node + * @param childAssocRef the primary parent-child association of the deleted node + * @param isNodeArchived indicates whether the node has been archived rather than purged */ - public void onDeleteNode(ChildAssociationRef childAssocRef); + public void onDeleteNode(ChildAssociationRef childAssocRef, boolean isNodeArchived); } public interface BeforeAddAspectPolicy extends ClassPolicy diff --git a/source/java/org/alfresco/repo/node/TemporaryAspect.java b/source/java/org/alfresco/repo/node/TemporaryAspect.java new file mode 100644 index 0000000000..ca8a5610fb --- /dev/null +++ b/source/java/org/alfresco/repo/node/TemporaryAspect.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.node; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.copy.CopyServicePolicies; +import org.alfresco.repo.policy.JavaBehaviour; +import org.alfresco.repo.policy.PolicyComponent; +import org.alfresco.repo.policy.PolicyScope; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; + +/** + * Registers and contains the behaviour specific to the + * {@link org.alfresco.model.ContentModel#ASPECT_TEMPORARY temporary aspect}. + * + * @author gavinc + */ +public class TemporaryAspect implements CopyServicePolicies.OnCopyNodePolicy +{ + // Dependencies + private PolicyComponent policyComponent; + + /** + * @param policyComponent the policy component to register behaviour with + */ + public void setPolicyComponent(PolicyComponent policyComponent) + { + this.policyComponent = policyComponent; + } + + /** + * Initialise the Temporary Aspect + *

+ * Ensures that the {@link ContentModel#ASPECT_TEMPORARY temporary aspect} + * copy behaviour is disabled when update copies are performed. + */ + public void init() + { + // disable copy for referencable aspect + this.policyComponent.bindClassBehaviour( + QName.createQName(NamespaceService.ALFRESCO_URI, "onCopyNode"), + ContentModel.ASPECT_TEMPORARY, + new JavaBehaviour(this, "onCopyNode")); + } + + /** + * Does nothing + */ + public void onCopyNode( + QName classRef, + NodeRef sourceNodeRef, + StoreRef destinationStoreRef, + boolean copyToNewNode, + PolicyScope copyDetails) + { + if (copyToNewNode) + { + copyDetails.addAspect(ContentModel.ASPECT_TEMPORARY); + } + else + { + // don't copy if this is an update operation + } + } +} diff --git a/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java b/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java index 81fc7ecb7e..bfb145c78c 100644 --- a/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java +++ b/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java @@ -23,6 +23,7 @@ import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -41,7 +42,10 @@ import org.alfresco.repo.node.StoreArchiveMap; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.service.cmr.dictionary.AspectDefinition; +import org.alfresco.service.cmr.dictionary.AssociationDefinition; +import org.alfresco.service.cmr.dictionary.ChildAssociationDefinition; import org.alfresco.service.cmr.dictionary.ClassDefinition; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.InvalidAspectException; import org.alfresco.service.cmr.dictionary.InvalidTypeException; import org.alfresco.service.cmr.dictionary.PropertyDefinition; @@ -62,8 +66,10 @@ import org.alfresco.service.cmr.repository.NodeRef.Status; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.QNamePattern; +import org.alfresco.util.ParameterCheck; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.util.Assert; /** @@ -280,29 +286,35 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl String newId = generateGuid(properties); // create the node instance - Node node = nodeDaoService.newNode(store, newId, nodeTypeQName); + Node childNode = nodeDaoService.newNode(store, newId, nodeTypeQName); // get the parent node Node parentNode = getNodeNotNull(parentRef); - // create the association - invoke policy behaviour - ChildAssoc childAssoc = nodeDaoService.newChildAssoc(parentNode, node, true, assocTypeQName, assocQName); - ChildAssociationRef childAssocRef = childAssoc.getChildAssocRef(); - // Set the default property values addDefaultPropertyValues(nodeTypeDef, properties); // Add the default aspects to the node - addDefaultAspects(nodeTypeDef, node, childAssocRef.getChildRef(), properties); + addDefaultAspects(nodeTypeDef, childNode, properties); // set the properties - it is a new node so only set properties if there are any - Map propertiesBefore = getProperties(childAssocRef.getChildRef()); + Map propertiesBefore = getPropertiesImpl(childNode); Map propertiesAfter = null; if (properties.size() > 0) { - propertiesAfter = setPropertiesImpl(childAssocRef.getChildRef(), properties); + propertiesAfter = setPropertiesImpl(childNode, properties); } + // create the association + ChildAssoc childAssoc = nodeDaoService.newChildAssoc( + parentNode, + childNode, + true, + assocTypeQName, + assocQName); + setChildUniqueName(childNode); // ensure uniqueness + ChildAssociationRef childAssocRef = childAssoc.getChildAssocRef(); + // Invoke policy behaviour invokeOnCreateNode(childAssocRef); invokeOnUpdateNode(parentRef); @@ -320,8 +332,10 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl * * @param nodeTypeDef */ - private void addDefaultAspects(ClassDefinition classDefinition, Node node, NodeRef nodeRef, Map properties) + private void addDefaultAspects(ClassDefinition classDefinition, Node node, Map properties) { + NodeRef nodeRef = node.getNodeRef(); + // get the mandatory aspects for the node type List defaultAspectDefs = classDefinition.getDefaultAspects(); @@ -335,7 +349,7 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl invokeOnAddAspect(nodeRef, defaultAspectDef.getName()); // Now add any default aspects for this aspect - addDefaultAspects(defaultAspectDef, node, nodeRef, properties); + addDefaultAspects(defaultAspectDef, node, properties); } } @@ -387,8 +401,16 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl // remove the child assoc from the old parent // don't cascade as we will still need the node afterwards nodeDaoService.deleteChildAssoc(oldAssoc, false); + // create a new assoc - ChildAssoc newAssoc = nodeDaoService.newChildAssoc(newParentNode, nodeToMove, true, assocTypeQName, assocQName); + ChildAssoc newAssoc = nodeDaoService.newChildAssoc( + newParentNode, + nodeToMove, + true, + assocTypeQName, + assocQName); + setChildUniqueName(nodeToMove); // ensure uniqueness + ChildAssociationRef newAssocRef = newAssoc.getChildAssocRef(); // If the node is moving stores, then drag the node hierarchy with it if (movingStore) @@ -406,7 +428,9 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl // invoke policy behaviour if (movingStore) { - invokeOnDeleteNode(oldAssocRef, nodeToMoveTypeQName, nodeToMoveAspects); + // TODO for now indicate that the node has been archived to prevent the version history from being removed + // in the future a onMove policy could be added and remove the need for onDelete and onCreate to be fired here + invokeOnDeleteNode(oldAssocRef, nodeToMoveTypeQName, nodeToMoveAspects, true); invokeOnCreateNode(newAssoc.getChildAssocRef()); } else @@ -416,6 +440,7 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl invokeOnUpdateNode(oldParentNode.getNodeRef()); invokeOnUpdateNode(newParentRef); } + invokeOnMoveNode(oldAssocRef, newAssocRef); // update the node status nodeDaoService.recordChangeId(nodeToMoveRef); @@ -472,8 +497,8 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl node.setTypeQName(typeQName); // Add the default aspects to the node (update the properties with any new default values) - Map properties = this.getProperties(nodeRef); - addDefaultAspects(nodeTypeDef, node, nodeRef, properties); + Map properties = this.getPropertiesImpl(node); + addDefaultAspects(nodeTypeDef, node, properties); this.setProperties(nodeRef, properties); // Invoke policies @@ -503,7 +528,7 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl Node node = getNodeNotNull(nodeRef); // attach the properties to the current node properties - Map nodeProperties = getProperties(nodeRef); + Map nodeProperties = getPropertiesImpl(node); if (aspectProperties != null) { @@ -514,7 +539,7 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl addDefaultPropertyValues(aspectDef, nodeProperties); // Add any dependant aspect - addDefaultAspects(aspectDef, node, nodeRef, nodeProperties); + addDefaultAspects(aspectDef, node, nodeProperties); // Set the property values back on the node setProperties(nodeRef, nodeProperties); @@ -598,8 +623,11 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl public void deleteNode(NodeRef nodeRef) { - // Invoke policy behaviours - invokeBeforeDeleteNode(nodeRef); + boolean isArchivedNode = false; + boolean requiresDelete = false; + + // Invoke policy behaviours + invokeBeforeDeleteNode(nodeRef); // get the node Node node = getNodeNotNull(nodeRef); @@ -610,23 +638,41 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl Set nodeAspectQNames = node.getAspects(); // check if we need to archive the node - StoreRef storeRef = nodeRef.getStoreRef(); - StoreRef archiveStoreRef = storeArchiveMap.getArchiveMap().get(storeRef); - // get the type and check if we need archiving - TypeDefinition typeDef = dictionaryService.getType(node.getTypeQName()); - if (typeDef == null || !typeDef.isArchive() || archiveStoreRef == null) + StoreRef archiveStoreRef = null; + if (nodeAspectQNames.contains(ContentModel.ASPECT_TEMPORARY)) + { + // the node has the temporary aspect meaning + // it can not be archived + requiresDelete = true; + isArchivedNode = false; + } + else + { + StoreRef storeRef = nodeRef.getStoreRef(); + archiveStoreRef = storeArchiveMap.getArchiveMap().get(storeRef); + // get the type and check if we need archiving + TypeDefinition typeDef = dictionaryService.getType(node.getTypeQName()); + if (typeDef == null || !typeDef.isArchive() || archiveStoreRef == null) + { + requiresDelete = true; + } + } + + if (requiresDelete) { // perform a normal deletion nodeDaoService.deleteNode(node, true); + isArchivedNode = false; } else { // archive it archiveNode(nodeRef, archiveStoreRef); + isArchivedNode = true; } // Invoke policy behaviours - invokeOnDeleteNode(childAssocRef, nodeTypeQName, nodeAspectQNames); + invokeOnDeleteNode(childAssocRef, nodeTypeQName, nodeAspectQNames, isArchivedNode); } public ChildAssociationRef addChild(NodeRef parentRef, NodeRef childRef, QName assocTypeQName, QName assocQName) @@ -635,21 +681,19 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl invokeBeforeUpdateNode(parentRef); invokeBeforeCreateChildAssociation(parentRef, childRef, assocTypeQName, assocQName); - // check that both nodes belong to the same store - if (!parentRef.getStoreRef().equals(childRef.getStoreRef())) - { - throw new InvalidNodeRefException("Parent and child nodes must belong to the same store: \n" + - " parent: " + parentRef + "\n" + - " child: " + childRef, - childRef); - } - // get the parent node and ensure that it is a container node Node parentNode = getNodeNotNull(parentRef); // get the child node Node childNode = getNodeNotNull(childRef); // make the association - ChildAssoc assoc = nodeDaoService.newChildAssoc(parentNode, childNode, false, assocTypeQName, assocQName); + ChildAssoc assoc = nodeDaoService.newChildAssoc( + parentNode, + childNode, + false, + assocTypeQName, + assocQName); + // ensure name uniqueness + setChildUniqueName(childNode); ChildAssociationRef assocRef = assoc.getChildAssocRef(); NodeRef childNodeRef = assocRef.getChildRef(); @@ -672,7 +716,7 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl // get all the child assocs ChildAssociationRef primaryAssocRef = null; - Collection assocs = parentNode.getChildAssocs(); + Collection assocs = nodeDaoService.getChildAssocs(parentNode); assocs = new HashSet(assocs); // copy set as we will be modifying it for (ChildAssoc assoc : assocs) { @@ -710,6 +754,13 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl public Map getProperties(NodeRef nodeRef) throws InvalidNodeRefException { Node node = getNodeNotNull(nodeRef); + return getPropertiesImpl(node); + } + + private Map getPropertiesImpl(Node node) throws InvalidNodeRefException + { + NodeRef nodeRef = node.getNodeRef(); + Map nodeProperties = node.getProperties(); Map ret = new HashMap(nodeProperties.size()); // copy values @@ -779,12 +830,16 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl */ public void setProperties(NodeRef nodeRef, Map properties) throws InvalidNodeRefException { - // Invoke policy behaviours + Node node = getNodeNotNull(nodeRef); + + // Invoke policy behaviours invokeBeforeUpdateNode(nodeRef); // Do the set properties - Map propertiesBefore = getProperties(nodeRef); - Map propertiesAfter = setPropertiesImpl(nodeRef, properties); + Map propertiesBefore = getPropertiesImpl(node); + Map propertiesAfter = setPropertiesImpl(node, properties); + + setChildUniqueName(node); // ensure uniqueness // Invoke policy behaviours invokeOnUpdateNode(nodeRef); @@ -795,24 +850,18 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl * Does the work of setting the property values. Returns a map containing the state of the properties after the set * operation is complete. * - * @param nodeRef the node reference + * @param node the node * @param properties the map of property values * @return the map of property values after the set operation is complete * @throws InvalidNodeRefException */ - private Map setPropertiesImpl(NodeRef nodeRef, Map properties) throws InvalidNodeRefException + private Map setPropertiesImpl(Node node, Map properties) throws InvalidNodeRefException { - if (properties == null) - { - throw new IllegalArgumentException("Properties may not be null"); - } + ParameterCheck.mandatory("properties", properties); // remove referencable properties removeReferencableProperties(properties); - // find the node - Node node = getNodeNotNull(nodeRef); - // copy properties onto node Map nodeProperties = node.getProperties(); nodeProperties.clear(); @@ -828,6 +877,7 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl } // update the node status + NodeRef nodeRef = node.getNodeRef(); nodeDaoService.recordChangeId(nodeRef); // Return the properties after @@ -847,10 +897,18 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl // Invoke policy behaviours invokeBeforeUpdateNode(nodeRef); - // Do the set operation - Map propertiesBefore = getProperties(nodeRef); - Map propertiesAfter = setPropertyImpl(nodeRef, qname, value); + // get the node + Node node = getNodeNotNull(nodeRef); + // Do the set operation + Map propertiesBefore = getPropertiesImpl(node); + Map propertiesAfter = setPropertyImpl(node, qname, value); + + if (qname.equals(ContentModel.PROP_NAME)) + { + setChildUniqueName(node); // ensure uniqueness + } + // Invoke policy behaviours invokeOnUpdateNode(nodeRef); invokeOnUpdateProperties(nodeRef, propertiesBefore, propertiesAfter); @@ -860,16 +918,15 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl * Does the work of setting a property value. Returns the values of the properties after the set operation is * complete. * - * @param nodeRef the node reference + * @param node the node * @param qname the qname of the property * @param value the value of the property * @return the values of the properties after the set operation is complete * @throws InvalidNodeRefException */ - public Map setPropertyImpl(NodeRef nodeRef, QName qname, Serializable value) throws InvalidNodeRefException + public Map setPropertyImpl(Node node, QName qname, Serializable value) throws InvalidNodeRefException { - // get the node - Node node = getNodeNotNull(nodeRef); + NodeRef nodeRef = node.getNodeRef(); Map properties = node.getProperties(); PropertyDefinition propertyDef = dictionaryService.getProperty(qname); @@ -880,7 +937,7 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl // update the node status nodeDaoService.recordChangeId(nodeRef); - return getProperties(nodeRef); + return getPropertiesImpl(node); } /** @@ -939,36 +996,50 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl { Node node = getNodeNotNull(nodeRef); // get the assocs pointing from it - Collection childAssocs = node.getChildAssocs(); + Collection childAssocRefs = nodeDaoService.getChildAssocRefs(node); // shortcut if there are no assocs - if (childAssocs.size() == 0) + if (childAssocRefs.size() == 0) { return Collections.emptyList(); } // sort results - ArrayList orderedList = new ArrayList(childAssocs); + ArrayList orderedList = new ArrayList(childAssocRefs); Collections.sort(orderedList); // list of results - List results = new ArrayList(childAssocs.size()); int nthSibling = 0; - for (ChildAssoc assoc : orderedList) + Iterator iterator = orderedList.iterator(); + while(iterator.hasNext()) { + ChildAssociationRef childAssocRef = iterator.next(); // does the qname match the pattern? - if (!qnamePattern.isMatch(assoc.getQname()) || !typeQNamePattern.isMatch(assoc.getTypeQName())) + if (!qnamePattern.isMatch(childAssocRef.getQName()) || !typeQNamePattern.isMatch(childAssocRef.getTypeQName())) { - // no match - ignore - continue; + // no match - remove + iterator.remove(); + } + else + { + childAssocRef.setNthSibling(nthSibling); + nthSibling++; } - ChildAssociationRef assocRef = assoc.getChildAssocRef(); - // slot the value in the right spot - assocRef.setNthSibling(nthSibling); - nthSibling++; - // get the child - results.add(assoc.getChildAssocRef()); } // done - return results; + return orderedList; + } + + public NodeRef getChildByName(NodeRef nodeRef, QName assocTypeQName, String childName) + { + Node node = getNodeNotNull(nodeRef); + ChildAssoc childAssoc = nodeDaoService.getChildAssoc(node, assocTypeQName, childName); + if (childAssoc != null) + { + return childAssoc.getChild().getNodeRef(); + } + else + { + return null; + } } public ChildAssociationRef getPrimaryParent(NodeRef nodeRef) throws InvalidNodeRefException @@ -1041,11 +1112,10 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl } public List getTargetAssocs(NodeRef sourceRef, QNamePattern qnamePattern) - throws InvalidNodeRefException { Node sourceNode = getNodeNotNull(sourceRef); // get all assocs to target - Collection assocs = sourceNode.getTargetNodeAssocs(); + Collection assocs = nodeDaoService.getTargetNodeAssocs(sourceNode); List nodeAssocRefs = new ArrayList(assocs.size()); for (NodeAssoc assoc : assocs) { @@ -1061,11 +1131,10 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl } public List getSourceAssocs(NodeRef targetRef, QNamePattern qnamePattern) - throws InvalidNodeRefException { - Node sourceNode = getNodeNotNull(targetRef); + Node targetNode = getNodeNotNull(targetRef); // get all assocs to source - Collection assocs = sourceNode.getSourceNodeAssocs(); + Collection assocs = nodeDaoService.getSourceNodeAssocs(targetNode); List nodeAssocRefs = new ArrayList(assocs.size()); for (NodeAssoc assoc : assocs) { @@ -1367,7 +1436,7 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl // add the node to the map nodesById.put(id, node); // recurse into the primary children - Collection childAssocs = node.getChildAssocs(); + Collection childAssocs = nodeDaoService.getChildAssocs(node); for (ChildAssoc childAssoc : childAssocs) { // cascade into primary associations @@ -1394,7 +1463,8 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl List childAssocsToDelete = new ArrayList(5); // child associations ArrayList archivedChildAssocRefs = new ArrayList(5); - for (ChildAssoc assoc : node.getChildAssocs()) + Collection childAssocs = nodeDaoService.getChildAssocs(node); + for (ChildAssoc assoc : childAssocs) { Long relatedNodeId = assoc.getChild().getId(); if (nodesById.containsKey(relatedNodeId)) @@ -1427,7 +1497,7 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl List nodeAssocsToDelete = new ArrayList(5); // source associations ArrayList archivedSourceAssocRefs = new ArrayList(5); - for (NodeAssoc assoc : node.getSourceNodeAssocs()) + for (NodeAssoc assoc : nodeDaoService.getSourceNodeAssocs(node)) { Long relatedNodeId = assoc.getSource().getId(); if (nodesById.containsKey(relatedNodeId)) @@ -1440,7 +1510,7 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl } // target associations ArrayList archivedTargetAssocRefs = new ArrayList(5); - for (NodeAssoc assoc : node.getTargetNodeAssocs()) + for (NodeAssoc assoc : nodeDaoService.getTargetNodeAssocs(node)) { Long relatedNodeId = assoc.getTarget().getId(); if (nodesById.containsKey(relatedNodeId)) @@ -1599,10 +1669,21 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl continue; } Node parentNode = getNodeNotNull(parentNodeRef); - nodeDaoService.newChildAssoc(parentNode, node, assocRef.isPrimary(), assocRef.getTypeQName(), assocRef.getQName()); + // get the name to use for the unique child check + QName assocTypeQName = assocRef.getTypeQName(); + nodeDaoService.newChildAssoc( + parentNode, + node, + assocRef.isPrimary(), + assocTypeQName, + assocRef.getQName()); } properties.remove(ContentModel.PROP_ARCHIVED_PARENT_ASSOCS); } + + // make sure that the node name uniqueness is enforced + setChildUniqueName(node); + // restore child associations Collection childAssocRefs = (Collection) getProperty( nodeRef, @@ -1617,7 +1698,16 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl continue; } Node childNode = getNodeNotNull(childNodeRef); - nodeDaoService.newChildAssoc(node, childNode, assocRef.isPrimary(), assocRef.getTypeQName(), assocRef.getQName()); + QName assocTypeQName = assocRef.getTypeQName(); + // get the name to use for the unique child check + nodeDaoService.newChildAssoc( + node, + childNode, + assocRef.isPrimary(), + assocTypeQName, + assocRef.getQName()); + // ensure that the name uniqueness is enforced for the child node + setChildUniqueName(childNode); } properties.remove(ContentModel.PROP_ARCHIVED_CHILD_ASSOCS); } @@ -1660,4 +1750,56 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl // remove the aspect node.getAspects().remove(ContentModel.ASPECT_ARCHIVED_ASSOCS); } + + /** + * Checks the dictionary's definition of the association to assign a unique name to the child node. + * + * @param assocTypeQName the type of the child association + * @param childNode the child node being added. The name will be extracted from it, if necessary. + * @return Returns the value to be put on the child association for uniqueness, or null if + */ + private void setChildUniqueName(Node childNode) + { + // get the name property + Map properties = childNode.getProperties(); + PropertyValue nameValue = properties.get(ContentModel.PROP_NAME); + String useName = null; + if (nameValue == null) + { + // no name has been assigned, so assign the ID of the child node + useName = childNode.getUuid(); + } + else + { + useName = (String) nameValue.getValue(DataTypeDefinition.TEXT); + } + // get all the parent assocs + Collection parentAssocs = childNode.getParentAssocs(); + for (ChildAssoc assoc : parentAssocs) + { + QName assocTypeQName = assoc.getTypeQName(); + AssociationDefinition assocDef = dictionaryService.getAssociation(assocTypeQName); + if (!assocDef.isChild()) + { + throw new DataIntegrityViolationException("Child association has non-child type: " + assoc.getId()); + } + ChildAssociationDefinition childAssocDef = (ChildAssociationDefinition) assocDef; + if (childAssocDef.getDuplicateChildNamesAllowed()) + { + // the name is irrelevant, so it doesn't need to be put into the unique key + nodeDaoService.setChildNameUnique(assoc, null); + } + else + { + nodeDaoService.setChildNameUnique(assoc, useName); + } + } + // done + if (logger.isDebugEnabled()) + { + logger.debug( + "Unique name set for all " + parentAssocs.size() + " parent associations: \n" + + " name: " + useName); + } + } } diff --git a/source/java/org/alfresco/repo/node/db/NodeDaoService.java b/source/java/org/alfresco/repo/node/db/NodeDaoService.java index 474ba27a81..3fba9af4f9 100644 --- a/source/java/org/alfresco/repo/node/db/NodeDaoService.java +++ b/source/java/org/alfresco/repo/node/db/NodeDaoService.java @@ -16,6 +16,7 @@ */ package org.alfresco.repo.node.db; +import java.util.Collection; import java.util.List; import org.alfresco.repo.domain.ChildAssoc; @@ -24,6 +25,7 @@ import org.alfresco.repo.domain.NodeAssoc; import org.alfresco.repo.domain.NodeStatus; import org.alfresco.repo.domain.Store; import org.alfresco.service.cmr.dictionary.InvalidTypeException; +import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.namespace.QName; @@ -128,6 +130,30 @@ public interface NodeDaoService QName assocTypeQName, QName qname); + /** + * Change the name of the child node. + * + * @param childAssoc the child association to change + * @param childName the name to put on the association + */ + public void setChildNameUnique(ChildAssoc childAssoc, String childName); + + /** + * Get all child associations for a given node + * + * @param parentNode the parent of the child associations + * @return Returns all child associations for the given node + */ + public Collection getChildAssocs(final Node parentNode); + + /** + * Get a collection of all child association references for a given parent node. + * + * @param parentNode the parent node + * @return Returns a collection of association references + */ + public Collection getChildAssocRefs(Node parentNode); + /** * @return Returns a matching association or null if one was not found * @@ -138,7 +164,12 @@ public interface NodeDaoService Node childNode, QName assocTypeQName, QName qname); - + + /** + * @return Returns an association matching the given parent, type and child name - or null + * if not found + */ + public ChildAssoc getChildAssoc(Node parentNode, QName assocTypeQName, String childName); /** * @param assoc the child association to remove @@ -164,6 +195,11 @@ public interface NodeDaoService Node targetNode, QName assocTypeQName); + /** + * @return Returns a list of all node associations associated with the given node + */ + public List getNodeAssocsToAndFrom(final Node node); + /** * @return Returns the node association or null if not found */ @@ -172,6 +208,16 @@ public interface NodeDaoService Node targetNode, QName assocTypeQName); + /** + * @return Returns all the node associations where the node is the source + */ + public List getTargetNodeAssocs(Node sourceNode); + + /** + * @return Returns all the node associations where the node is the target + */ + public List getSourceNodeAssocs(Node targetNode); + /** * @param assoc the node association to remove */ diff --git a/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java b/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java index 3c582044ac..6ecccd65d1 100644 --- a/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java +++ b/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java @@ -19,7 +19,9 @@ package org.alfresco.repo.node.db.hibernate; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.zip.CRC32; +import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.repo.domain.ChildAssoc; import org.alfresco.repo.domain.Node; @@ -37,9 +39,11 @@ import org.alfresco.repo.node.db.NodeDaoService; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.TransactionalDao; import org.alfresco.service.cmr.dictionary.InvalidTypeException; -import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.cmr.repository.AssociationExistsException; import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.DuplicateChildNodeNameException; import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.namespace.QName; import org.alfresco.util.GUID; import org.hibernate.ObjectDeletedException; @@ -58,6 +62,14 @@ import org.springframework.orm.hibernate3.support.HibernateDaoSupport; public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements NodeDaoService, TransactionalDao { private static final String QUERY_GET_ALL_STORES = "store.GetAllStores"; + private static final String UPDATE_SET_CHILD_ASSOC_NAME = "node.updateChildAssocName"; + private static final String QUERY_GET_CHILD_ASSOCS = "node.GetChildAssocs"; + private static final String QUERY_GET_CHILD_ASSOC_BY_TYPE_AND_NAME = "node.GetChildAssocByTypeAndName"; + private static final String QUERY_GET_CHILD_ASSOC_REFS = "node.GetChildAssocRefs"; + private static final String QUERY_GET_NODE_ASSOC = "node.GetNodeAssoc"; + private static final String QUERY_GET_NODE_ASSOCS_TO_AND_FROM = "node.GetNodeAssocsToAndFrom"; + private static final String QUERY_GET_TARGET_ASSOCS = "node.GetTargetAssocs"; + private static final String QUERY_GET_SOURCE_ASSOCS = "node.GetSourceAssocs"; private static final String QUERY_GET_CONTENT_DATA_STRINGS = "node.GetContentDataStrings"; /** a uuid identifying this unique instance */ @@ -303,25 +315,17 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements deleteChildAssoc(assoc, false); // we don't cascade upwards } // delete all child assocs - Collection childAssocs = node.getChildAssocs(); + Collection childAssocs = getChildAssocs(node); childAssocs = new ArrayList(childAssocs); for (ChildAssoc assoc : childAssocs) { deleteChildAssoc(assoc, cascade); // potentially cascade downwards } - // delete all target assocs - Collection targetAssocs = node.getTargetNodeAssocs(); - targetAssocs = new ArrayList(targetAssocs); - for (NodeAssoc assoc : targetAssocs) + // delete all node associations to and from + List nodeAssocs = getNodeAssocsToAndFrom(node); + for (NodeAssoc assoc : nodeAssocs) { - deleteNodeAssoc(assoc); - } - // delete all source assocs - Collection sourceAssocs = node.getSourceNodeAssocs(); - sourceAssocs = new ArrayList(sourceAssocs); - for (NodeAssoc assoc : sourceAssocs) - { - deleteNodeAssoc(assoc); + getHibernateTemplate().delete(assoc); } // update the node status NodeRef nodeRef = node.getNodeRef(); @@ -330,9 +334,34 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements nodeStatus.setChangeTxnId(AlfrescoTransactionSupport.getTransactionId()); // finally delete the node getHibernateTemplate().delete(node); + // flush to ensure constraints can't be violated + getSession().flush(); // done } - + + private long getCrc(String str) + { + CRC32 crc = new CRC32(); + crc.update(str.getBytes()); + return crc.getValue(); + } + + private static final String TRUNCATED_NAME_INDICATOR = "~~~"; + private String getShortName(String str) + { + int length = str.length(); + if (length <= 50) + { + return str; + } + else + { + StringBuilder ret = new StringBuilder(50); + ret.append(str.substring(0, 47)).append(TRUNCATED_NAME_INDICATOR); + return ret.toString(); + } + } + public ChildAssoc newChildAssoc( Node parentNode, Node childNode, @@ -340,17 +369,170 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements QName assocTypeQName, QName qname) { + /* + * This initial child association creation will fail IFF there is already + * an association of the given type between the two nodes. For new association + * creation, this can only occur if two transactions attempt to create a secondary + * child association between the same two nodes. As this is unlikely, it is + * appropriate to just throw a runtime exception and let the second transaction + * fail. + * + * We don't need to flush the session beforehand as there won't be any deletions + * of the assocation pending. The association deletes, on the other hand, have + * to flush early in order to ensure that the database unique index isn't violated + * if the association is recreated subsequently. + */ + + String uuid = childNode.getUuid(); + ChildAssoc assoc = new ChildAssocImpl(); assoc.setTypeQName(assocTypeQName); - assoc.setIsPrimary(isPrimary); + assoc.setChildNodeName(getShortName(uuid)); + assoc.setChildNodeNameCrc(getCrc(uuid)); assoc.setQname(qname); + assoc.setIsPrimary(isPrimary); assoc.buildAssociation(parentNode, childNode); - // persist - getHibernateTemplate().save(assoc); + // persist it, catching the duplicate child name + try + { + getHibernateTemplate().save(assoc); + } + catch (DataIntegrityViolationException e) + { + throw new AlfrescoRuntimeException("An association already exists between the two nodes: \n" + + " parent: " + parentNode.getId() + "\n" + + " child: " + childNode.getId() + "\n" + + " assoc: " + assocTypeQName, + e); + } // done return assoc; } + public void setChildNameUnique(final ChildAssoc childAssoc, String childName) + { + /* + * As the Hibernate session is rendered useless when an exception is + * bubbled up, we go direct to the database to update the child association. + * This preserves the session and client code can catch the resulting + * exception and react to it whilst in the same transaction. + * + * We ensure that case-insensitivity is maintained by persisting + * the lowercase version of the child node name. + */ + + String childNameNew = null; + if (childName == null) + { + childNameNew = childAssoc.getChild().getUuid(); + } + else + { + childNameNew = childName.toLowerCase(); + } + + final String childNameNewShort = getShortName(childNameNew); + final long childNameNewCrc = getCrc(childNameNew); + + // check if the name has changed + if (childAssoc.getChildNodeNameCrc() == childNameNewCrc) + { + if (childAssoc.getChildNodeName().equals(childNameNewShort)) + { + // nothing changed + return; + } + } + + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + session.flush(); + Query query = session + .getNamedQuery(HibernateNodeDaoServiceImpl.UPDATE_SET_CHILD_ASSOC_NAME) + .setString("newName", childNameNewShort) + .setLong("newNameCrc", childNameNewCrc) + .setLong("childAssocId", childAssoc.getId()); + return (Integer) query.executeUpdate(); + } + }; + try + { + Integer count = (Integer) getHibernateTemplate().execute(callback); + // refresh the entity directly + getHibernateTemplate().refresh(childAssoc); + if (count.intValue() == 0) + { + if (logger.isDebugEnabled()) + { + logger.debug("ChildAssoc not updated: " + childAssoc.getId()); + } + } + } + catch (DataIntegrityViolationException e) + { + NodeRef parentNodeRef = childAssoc.getParent().getNodeRef(); + QName assocTypeQName = childAssoc.getTypeQName(); + throw new DuplicateChildNodeNameException(parentNodeRef, assocTypeQName, childName); + } + } + + @SuppressWarnings("unchecked") + public Collection getChildAssocs(final Node parentNode) + { + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + Query query = session + .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_CHILD_ASSOCS) + .setLong("parentId", parentNode.getId()); + return query.list(); + } + }; + List queryResults = (List) getHibernateTemplate().execute(callback); + return queryResults; + } + + @SuppressWarnings("unchecked") + public Collection getChildAssocRefs(final Node parentNode) + { + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + Query query = session + .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_CHILD_ASSOC_REFS) + .setLong("parentId", parentNode.getId()); + return query.list(); + } + }; + List queryResults = (List) getHibernateTemplate().execute(callback); + Collection refs = new ArrayList(queryResults.size()); + NodeRef parentNodeRef = parentNode.getNodeRef(); + for (Object[] row : queryResults) + { + String childProtocol = (String) row[5]; + String childIdentifier = (String) row[6]; + String childUuid = (String) row[7]; + NodeRef childNodeRef = new NodeRef(new StoreRef(childProtocol, childIdentifier), childUuid); + QName assocTypeQName = (QName) row[0]; + QName assocQName = (QName) row[1]; + Boolean assocIsPrimary = (Boolean) row[2]; + Integer assocIndex = (Integer) row[3]; + ChildAssociationRef assocRef = new ChildAssociationRef( + assocTypeQName, + parentNodeRef, + assocQName, + childNodeRef, + assocIsPrimary.booleanValue(), + assocIndex.intValue()); + refs.add(assocRef); + } + return refs; + } + public ChildAssoc getChildAssoc( Node parentNode, Node childNode, @@ -363,7 +545,7 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements qname, childNode.getNodeRef()); // get all the parent's child associations - Collection assocs = parentNode.getChildAssocs(); + Collection assocs = getChildAssocs(parentNode); // hunt down the desired assoc for (ChildAssoc assoc : assocs) { @@ -381,6 +563,28 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements return null; } + public ChildAssoc getChildAssoc(final Node parentNode, final QName assocTypeQName, final String childName) + { + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + String childNameLower = childName.toLowerCase(); + String childNameShort = getShortName(childNameLower); + long childNameLowerCrc = getCrc(childNameLower); + Query query = session + .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_CHILD_ASSOC_BY_TYPE_AND_NAME) + .setLong("parentId", parentNode.getId()) + .setParameter("typeQName", assocTypeQName) + .setParameter("childNodeName", childNameShort) + .setLong("childNodeNameCrc", childNameLowerCrc); + return query.uniqueResult(); + } + }; + ChildAssoc childAssoc = (ChildAssoc) getHibernateTemplate().execute(callback); + return childAssoc; + } + /** * Manually enforces cascade deletions down primary associations */ @@ -403,6 +607,10 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements * duplicate call will be received to do this */ } + + // To ensure the validity of the constraint enforcement by the database, + // we have to flush here + getSession().flush(); } public ChildAssoc getPrimaryParentAssoc(Node node) @@ -458,45 +666,101 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements assoc.setTypeQName(assocTypeQName); assoc.buildAssociation(sourceNode, targetNode); // persist - getHibernateTemplate().save(assoc); + try + { + getHibernateTemplate().save(assoc); + } + catch (DataIntegrityViolationException e) + { + throw new AssociationExistsException( + sourceNode.getNodeRef(), + targetNode.getNodeRef(), + assocTypeQName, + e); + } // done return assoc; } + @SuppressWarnings("unchecked") + public List getNodeAssocsToAndFrom(final Node node) + { + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + Query query = session + .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_NODE_ASSOCS_TO_AND_FROM) + .setLong("nodeId", node.getId()); + return query.list(); + } + }; + List results = (List) getHibernateTemplate().execute(callback); + return results; + } + public NodeAssoc getNodeAssoc( final Node sourceNode, final Node targetNode, final QName assocTypeQName) { - AssociationRef nodeAssocRef = new AssociationRef( - sourceNode.getNodeRef(), - assocTypeQName, - targetNode.getNodeRef()); - // get all the source's target associations - Collection assocs = sourceNode.getTargetNodeAssocs(); - // hunt down the desired assoc - for (NodeAssoc assoc : assocs) + HibernateCallback callback = new HibernateCallback() { - // is it a match? - if (!assoc.getNodeAssocRef().equals(nodeAssocRef)) // not a match + public Object doInHibernate(Session session) { - continue; + Query query = session + .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_NODE_ASSOC) + .setLong("sourceId", sourceNode.getId()) + .setLong("targetId", targetNode.getId()) + .setParameter("assocTypeQName", assocTypeQName); + return query.uniqueResult(); } - else + }; + NodeAssoc result = (NodeAssoc) getHibernateTemplate().execute(callback); + return result; + } + + @SuppressWarnings("unchecked") + public List getTargetNodeAssocs(final Node sourceNode) + { + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) { - return assoc; + Query query = session + .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_TARGET_ASSOCS) + .setLong("sourceId", sourceNode.getId()); + return query.list(); } - } - // not found - return null; + }; + List queryResults = (List) getHibernateTemplate().execute(callback); + return queryResults; + } + + @SuppressWarnings("unchecked") + public List getSourceNodeAssocs(final Node targetNode) + { + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + Query query = session + .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_SOURCE_ASSOCS) + .setLong("targetId", targetNode.getId()); + return query.list(); + } + }; + List queryResults = (List) getHibernateTemplate().execute(callback); + return queryResults; } public void deleteNodeAssoc(NodeAssoc assoc) { - // maintain inverse association sets - assoc.removeAssociation(); - // remove instance + // Remove instance getHibernateTemplate().delete(assoc); + // Flush to ensure that the database constraints aren't violated if the assoc + // is recreated in the transaction + getSession().flush(); } @SuppressWarnings("unchecked") @@ -513,24 +777,4 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements List queryResults = (List) getHibernateTemplate().execute(callback); return queryResults; } -} - - - - - - - - - - - - - - - - - - - - +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/node/index/NodeIndexer.java b/source/java/org/alfresco/repo/node/index/NodeIndexer.java index aa7adb40f0..ae88617b35 100644 --- a/source/java/org/alfresco/repo/node/index/NodeIndexer.java +++ b/source/java/org/alfresco/repo/node/index/NodeIndexer.java @@ -96,7 +96,7 @@ public class NodeIndexer indexer.updateNode(nodeRef); } - public void onDeleteNode(ChildAssociationRef childAssocRef) + public void onDeleteNode(ChildAssociationRef childAssocRef, boolean isArchivedNode) { indexer.deleteNode(childAssocRef); } diff --git a/source/java/org/alfresco/repo/node/integrity/AssocSourceMultiplicityIntegrityEvent.java b/source/java/org/alfresco/repo/node/integrity/AssocSourceMultiplicityIntegrityEvent.java index 9f1d91bdc4..f25dfec434 100644 --- a/source/java/org/alfresco/repo/node/integrity/AssocSourceMultiplicityIntegrityEvent.java +++ b/source/java/org/alfresco/repo/node/integrity/AssocSourceMultiplicityIntegrityEvent.java @@ -145,7 +145,7 @@ public class AssocSourceMultiplicityIntegrityEvent extends AbstractIntegrityEven } if ((mandatory && actualSize == 0) || (!allowMany && actualSize > 1)) { - String parentOrSourceStr = (assocDef.isChild() ? "child" : "target"); + String parentOrSourceStr = (assocDef.isChild() ? "parent" : "source"); IntegrityRecord result = new IntegrityRecord( "The association " + parentOrSourceStr + " multiplicity has been violated: \n" + " Association: " + assocDef + "\n" + diff --git a/source/java/org/alfresco/repo/node/integrity/AssocTargetRoleIntegrityEvent.java b/source/java/org/alfresco/repo/node/integrity/AssocTargetRoleIntegrityEvent.java index 8661e22e38..a5b66172d5 100644 --- a/source/java/org/alfresco/repo/node/integrity/AssocTargetRoleIntegrityEvent.java +++ b/source/java/org/alfresco/repo/node/integrity/AssocTargetRoleIntegrityEvent.java @@ -21,14 +21,10 @@ import java.util.List; import org.alfresco.service.cmr.dictionary.AssociationDefinition; import org.alfresco.service.cmr.dictionary.ChildAssociationDefinition; import org.alfresco.service.cmr.dictionary.DictionaryService; -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.namespace.QName; import org.alfresco.service.namespace.RegexQNamePattern; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; /** * Event to check the association target role name @@ -37,8 +33,6 @@ import org.apache.commons.logging.LogFactory; */ public class AssocTargetRoleIntegrityEvent extends AbstractIntegrityEvent { - private static Log logger = LogFactory.getLog(AssocTargetRoleIntegrityEvent.class); - public AssocTargetRoleIntegrityEvent( NodeService nodeService, DictionaryService dictionaryService, @@ -51,7 +45,6 @@ public class AssocTargetRoleIntegrityEvent extends AbstractIntegrityEvent public void checkIntegrity(List eventResults) { - NodeRef sourceNodeRef = getNodeRef(); QName assocTypeQName = getTypeQName(); QName assocQName = getQName(); @@ -80,7 +73,6 @@ public class AssocTargetRoleIntegrityEvent extends AbstractIntegrityEvent // perform required checks checkAssocQNameRegex(eventResults, childAssocDef, assocQName); - checkAssocQNameDuplicate(eventResults, childAssocDef, sourceNodeRef, assocQName); } /** @@ -108,39 +100,4 @@ public class AssocTargetRoleIntegrityEvent extends AbstractIntegrityEvent } } } - - /** - * Checks that the association name matches the constraints imposed by the model. - */ - protected void checkAssocQNameDuplicate( - List eventResults, - ChildAssociationDefinition assocDef, - NodeRef sourceNodeRef, - QName assocQName) - { - if (assocDef.getDuplicateChildNamesAllowed()) - { - // nothing to do - return; - } - QName assocTypeQName = assocDef.getName(); - // see if there is another association with the same name - try - { - List childAssocs = nodeService.getChildAssocs(sourceNodeRef, assocTypeQName, assocQName); - // duplicates not allowed - if (childAssocs.size() > 1) - { - IntegrityRecord result = new IntegrityRecord( - "Duplicate child associations are not allowed: \n" + - " Association: " + assocDef + "\n" + - " Name: " + assocQName); - eventResults.add(result); - } - } - catch (InvalidNodeRefException e) - { - // node has gone - } - } } diff --git a/source/java/org/alfresco/repo/node/integrity/IntegrityChecker.java b/source/java/org/alfresco/repo/node/integrity/IntegrityChecker.java index 1af48b8f78..e93ff18425 100644 --- a/source/java/org/alfresco/repo/node/integrity/IntegrityChecker.java +++ b/source/java/org/alfresco/repo/node/integrity/IntegrityChecker.java @@ -335,7 +335,7 @@ public class IntegrityChecker /** * No checking performed: The association changes will be handled */ - public void onDeleteNode(ChildAssociationRef childAssocRef) + public void onDeleteNode(ChildAssociationRef childAssocRef, boolean isArchivedNode) { } diff --git a/source/java/org/alfresco/repo/ownable/impl/OwnableServiceTest.java b/source/java/org/alfresco/repo/ownable/impl/OwnableServiceTest.java index 1d84a9bc70..79b87c0336 100644 --- a/source/java/org/alfresco/repo/ownable/impl/OwnableServiceTest.java +++ b/source/java/org/alfresco/repo/ownable/impl/OwnableServiceTest.java @@ -139,16 +139,41 @@ public class OwnableServiceTest extends TestCase assertFalse(nodeService.hasAspect(testNode, ContentModel.ASPECT_OWNABLE)); assertTrue(dynamicAuthority.hasAuthority(testNode, "andy")); + permissionService.setInheritParentPermissions(testNode, false); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(rootNodeRef, PermissionService.TAKE_OWNERSHIP)); assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(rootNodeRef, PermissionService.SET_OWNER)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.TAKE_OWNERSHIP)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.SET_OWNER)); + + + + ownableService.setOwner(testNode, "woof"); + assertEquals("woof", ownableService.getOwner(testNode)); + assertTrue(dynamicAuthority.hasAuthority(testNode, "woof")); + assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, PermissionService.TAKE_OWNERSHIP)); + assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, PermissionService.SET_OWNER)); + ownableService.setOwner(testNode, "muppet"); assertEquals("muppet", ownableService.getOwner(testNode)); + assertTrue(dynamicAuthority.hasAuthority(testNode, "muppet")); + assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, PermissionService.TAKE_OWNERSHIP)); + assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, PermissionService.SET_OWNER)); + + ownableService.takeOwnership(testNode); assertEquals("andy", ownableService.getOwner(testNode)); + assertTrue(dynamicAuthority.hasAuthority(testNode, "andy")); assertTrue(nodeService.hasAspect(testNode, ContentModel.ASPECT_AUDITABLE)); assertTrue(nodeService.hasAspect(testNode, ContentModel.ASPECT_OWNABLE)); - assertTrue(dynamicAuthority.hasAuthority(testNode, "andy")); + + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(rootNodeRef, PermissionService.TAKE_OWNERSHIP)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(rootNodeRef, PermissionService.SET_OWNER)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.TAKE_OWNERSHIP)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.SET_OWNER)); + + } public void testContainer() diff --git a/source/java/org/alfresco/repo/rule/BaseRuleTest.java b/source/java/org/alfresco/repo/rule/BaseRuleTest.java index 18c5cb4cd4..332f0495dd 100644 --- a/source/java/org/alfresco/repo/rule/BaseRuleTest.java +++ b/source/java/org/alfresco/repo/rule/BaseRuleTest.java @@ -17,6 +17,7 @@ package org.alfresco.repo.rule; import java.io.Serializable; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -155,6 +156,11 @@ public class BaseRuleTest extends BaseSpringTest } protected Rule createTestRule(boolean isAppliedToChildren) + { + return createTestRule(isAppliedToChildren, TITLE); + } + + protected Rule createTestRule(boolean isAppliedToChildren, String title) { // Rule properties Map conditionProps = new HashMap(); @@ -163,33 +169,40 @@ public class BaseRuleTest extends BaseSpringTest Map actionProps = new HashMap(); actionProps.put(ACTION_PROP_NAME_1, ACTION_PROP_VALUE_1); - // Create the rule - Rule rule = this.ruleService.createRule(this.ruleType.getName()); - rule.setTitle(TITLE); - rule.setDescription(DESCRIPTION); - rule.applyToChildren(isAppliedToChildren); + List ruleTypes = new ArrayList(1); + ruleTypes.add(this.ruleType.getName()); + + // Create the action + Action action = this.actionService.createAction(CONDITION_DEF_NAME); + action.setParameterValues(conditionProps); ActionCondition actionCondition = this.actionService.createActionCondition(CONDITION_DEF_NAME); actionCondition.setParameterValues(conditionProps); - rule.addActionCondition(actionCondition); + action.addActionCondition(actionCondition); - Action action = this.actionService.createAction(CONDITION_DEF_NAME); - action.setParameterValues(conditionProps); - rule.addAction(action); + // Create the rule + Rule rule = new Rule(); + rule.setRuleTypes(ruleTypes); + rule.setTitle(title); + rule.setDescription(DESCRIPTION); + rule.applyToChildren(isAppliedToChildren); + rule.setAction(action); return rule; } - protected void checkRule(RuleImpl rule, String id) + protected void checkRule(Rule rule) { // Check the basic details of the rule - assertEquals(id, rule.getId()); - assertEquals(this.ruleType.getName(), rule.getRuleTypeName()); + assertEquals(this.ruleType.getName(), rule.getRuleTypes().get(0)); assertEquals(TITLE, rule.getTitle()); assertEquals(DESCRIPTION, rule.getDescription()); + Action ruleAction = rule.getAction(); + assertNotNull(ruleAction); + // Check conditions - List ruleConditions = rule.getActionConditions(); + List ruleConditions = ruleAction.getActionConditions(); assertNotNull(ruleConditions); assertEquals(1, ruleConditions.size()); assertEquals(CONDITION_DEF_NAME, ruleConditions.get(0) @@ -202,11 +215,8 @@ public class BaseRuleTest extends BaseSpringTest assertEquals(COND_PROP_VALUE_1, condParams.get(COND_PROP_NAME_1)); // Check the actions - List ruleActions = rule.getActions(); - assertNotNull(ruleActions); - assertEquals(1, ruleActions.size()); - assertEquals(ACTION_DEF_NAME, ruleActions.get(0).getActionDefinitionName()); - Map actionParams = ruleActions.get(0).getParameterValues(); + assertEquals(ACTION_DEF_NAME, ruleAction.getActionDefinitionName()); + Map actionParams = ruleAction.getParameterValues(); assertNotNull(actionParams); assertEquals(1, actionParams.size()); assertTrue(actionParams.containsKey(ACTION_PROP_NAME_1)); diff --git a/source/java/org/alfresco/repo/rule/RuleImpl.java b/source/java/org/alfresco/repo/rule/RuleImpl.java deleted file mode 100644 index 2bc76da74f..0000000000 --- a/source/java/org/alfresco/repo/rule/RuleImpl.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2005 Alfresco, Inc. - * - * Licensed under the Mozilla Public License version 1.1 - * with a permitted attribution clause. You may obtain a - * copy of the License at - * - * http://www.alfresco.org/legal/license.txt - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - * either express or implied. See the License for the specific - * language governing permissions and limitations under the - * License. - */ -package org.alfresco.repo.rule; - -import java.io.Serializable; - -import org.alfresco.repo.action.CompositeActionImpl; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.rule.Rule; -import org.alfresco.util.ParameterCheck; - -/** - * Rule implementation class. - *

- * Encapsulates all the information about a rule. Can be creted or editied and - * then passed to the rule service to create/update a rule instance. - * - * @author Roy Wetherall - */ -public class RuleImpl extends CompositeActionImpl implements Serializable, Rule -{ - /** - * Serial version UID - */ - private static final long serialVersionUID = 3544385898889097524L; - - /** - * The rule type name - */ - private String ruleTypeName; - - /** - * Indicates whether the rule is applied to all the children of the associated node - * rather than just the node itself. - */ - private boolean isAppliedToChildren = false; - - /** - * Constructor - * - * @param ruleTypeName the rule type name - */ - public RuleImpl(String id, String ruleTypeName, NodeRef owningNodeRef) - { - super(id, owningNodeRef); - ParameterCheck.mandatory("ruleTypeName", ruleTypeName); - - this.ruleTypeName = ruleTypeName; - } - - /** - * @see org.alfresco.service.cmr.rule.Rule#isAppliedToChildren() - */ - public boolean isAppliedToChildren() - { - return this.isAppliedToChildren; - } - - /** - *@see org.alfresco.service.cmr.rule.Rule#applyToChildren(boolean) - */ - public void applyToChildren(boolean isAppliedToChildren) - { - this.isAppliedToChildren = isAppliedToChildren; - } - - /** - * @see org.alfresco.service.cmr.rule.Rule#getRuleTypeName() - */ - public String getRuleTypeName() - { - return this.ruleTypeName; - } -} - diff --git a/source/java/org/alfresco/repo/rule/RuleModel.java b/source/java/org/alfresco/repo/rule/RuleModel.java index 6f2da46721..bb0d318cfd 100644 --- a/source/java/org/alfresco/repo/rule/RuleModel.java +++ b/source/java/org/alfresco/repo/rule/RuleModel.java @@ -10,12 +10,18 @@ import org.alfresco.service.namespace.QName; public interface RuleModel { /** Rule model constants */ - static final String RULE_MODEL_URI = "http://www.alfresco.org/model/rule/1.0"; - static final String RULE_MODEL_PREFIX = "rule"; - static final QName TYPE_RULE = QName.createQName(RULE_MODEL_URI, "rule"); - static final QName PROP_RULE_TYPE = QName.createQName(RULE_MODEL_URI, "ruleType"); - static final QName TYPE_RULE_CONTENT = QName.createQName(RULE_MODEL_URI, "rulecontent"); - static final QName PROP_APPLY_TO_CHILDREN = QName.createQName(RULE_MODEL_URI, "applyToChildren"); - static final QName ASPECT_RULES = QName.createQName(RULE_MODEL_URI, "rules"); - static final QName ASSOC_RULE_FOLDER = QName.createQName(RULE_MODEL_URI, "ruleFolder"); + static final String RULE_MODEL_URI = "http://www.alfresco.org/model/rule/1.0"; + static final String RULE_MODEL_PREFIX = "rule"; + + static final QName TYPE_RULE = QName.createQName(RULE_MODEL_URI, "rule"); + static final QName PROP_RULE_TYPE = QName.createQName(RULE_MODEL_URI, "ruleType"); + static final QName PROP_APPLY_TO_CHILDREN = QName.createQName(RULE_MODEL_URI, "applyToChildren"); + static final QName PROP_EXECUTE_ASYNC = QName.createQName(RULE_MODEL_URI, "executeAsynchronously"); + static final QName ASSOC_ACTION = QName.createQName(RULE_MODEL_URI, "action"); + static final QName PROP_DISABLED = QName.createQName(RULE_MODEL_URI, "disabled"); + + static final QName ASPECT_RULES = QName.createQName(RULE_MODEL_URI, "rules"); + static final QName ASSOC_RULE_FOLDER = QName.createQName(RULE_MODEL_URI, "ruleFolder"); + + static final QName ASPECT_IGNORE_INHERITED_RULES = QName.createQName(RULE_MODEL_URI, "ignoreInheritedRules"); } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/rule/RuleServiceCoverageTest.java b/source/java/org/alfresco/repo/rule/RuleServiceCoverageTest.java index 284909b402..c00ed1e840 100644 --- a/source/java/org/alfresco/repo/rule/RuleServiceCoverageTest.java +++ b/source/java/org/alfresco/repo/rule/RuleServiceCoverageTest.java @@ -81,13 +81,11 @@ import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.util.StopWatch; /** - * @author Roy Wetherall + * @author Roy Wetherall */ public class RuleServiceCoverageTest extends TestCase { - //private static final ContentData CONTENT_DATA_TEXT = new ContentData(null, MimetypeMap.MIMETYPE_TEXT_PLAIN, 0L, "UTF-8"); - - /** + /** * Application context used during the test */ static ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:alfresco/application-context.xml"); @@ -109,6 +107,7 @@ public class RuleServiceCoverageTest extends TestCase private ActionService actionService; private ContentTransformerRegistry transformerRegistry; private CopyService copyService; + private AuthenticationComponent authenticationComponent; /** * Category related values @@ -147,9 +146,10 @@ public class RuleServiceCoverageTest extends TestCase this.actionService = serviceRegistry.getActionService(); this.transactionService = serviceRegistry.getTransactionService(); this.transformerRegistry = (ContentTransformerRegistry)applicationContext.getBean("contentTransformerRegistry"); + this.authenticationComponent = (AuthenticationComponent)applicationContext.getBean("authenticationComponent"); - AuthenticationComponent authenticationComponent = (AuthenticationComponent)applicationContext.getBean("authenticationComponent"); - authenticationComponent.setCurrentUser(authenticationComponent.getSystemUserName()); + //authenticationComponent.setCurrentUser(authenticationComponent.getSystemUserName()); + authenticationComponent.setSystemUserAsCurrentUser(); this.testStoreRef = this.nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, "Test_" + System.currentTimeMillis()); this.rootNodeRef = this.nodeService.getRootNode(this.testStoreRef); @@ -159,11 +159,7 @@ public class RuleServiceCoverageTest extends TestCase this.rootNodeRef, ContentModel.ASSOC_CHILDREN, ContentModel.ASSOC_CHILDREN, - ContentModel.TYPE_CONTAINER).getChildRef(); - - // Create and authenticate the user used in the tests - //TestWithUserUtils.createUser(USER_NAME, PWD, this.rootNodeRef, this.nodeService, this.authenticationService); - //TestWithUserUtils.authenticateUser(USER_NAME, PWD, this.rootNodeRef, this.authenticationService); + ContentModel.TYPE_CONTAINER).getChildRef(); } private Rule createRule( @@ -173,11 +169,14 @@ public class RuleServiceCoverageTest extends TestCase String conditionName, Map conditionParams) { - Rule rule = this.ruleService.createRule(ruleTypeName); + Rule rule = new Rule(); + rule.setRuleType(ruleTypeName); + + Action action = this.actionService.createAction(actionName, actionParams); ActionCondition condition = this.actionService.createActionCondition(conditionName, conditionParams); - rule.addActionCondition(condition); - Action action = this.actionService.createAction(actionName, actionParams); - rule.addAction(action); + action.addActionCondition(condition); + rule.setAction(action); + return rule; } @@ -226,8 +225,8 @@ public class RuleServiceCoverageTest extends TestCase /** * Check async rule execution */ - public void testAsyncRuleExecution() - { + public void testAsyncRuleExecution() + { final NodeRef newNodeRef = TransactionUtil.executeInUserTransaction( this.transactionService, new TransactionUtil.TransactionWork() @@ -323,9 +322,9 @@ public class RuleServiceCoverageTest extends TestCase params2.put(ContentModel.PROP_APPROVE_MOVE.toString(), false); // Test that rule can be updated and execute correctly - rule.removeAllActions(); + //rule.removeAllActions(); Action action2 = this.actionService.createAction(AddFeaturesActionExecuter.NAME, params2); - rule.addAction(action2); + rule.setAction(action2); this.ruleService.saveRule(this.nodeRef, rule); NodeRef newNodeRef2 = this.nodeService.createNode( @@ -342,6 +341,46 @@ public class RuleServiceCoverageTest extends TestCase // System.out.println(NodeStoreInspector.dumpNodeStore(this.nodeService, this.testStoreRef)); } + public void testDisableIndividualRules() + { + Map params = new HashMap(1); + params.put("aspect-name", ContentModel.ASPECT_CONFIGURABLE); + + Rule rule = createRule( + RuleType.INBOUND, + AddFeaturesActionExecuter.NAME, + params, + NoConditionEvaluator.NAME, + null); + rule.setRuleDisabled(true); + + this.ruleService.saveRule(this.nodeRef, rule); + + NodeRef newNodeRef = this.nodeService.createNode( + this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "children"), + ContentModel.TYPE_CONTENT, + getContentProperties()).getChildRef(); + addContentToNode(newNodeRef); + assertFalse(this.nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_CONFIGURABLE)); + + Rule rule2 = this.ruleService.getRule(rule.getNodeRef()); + rule2.setRuleDisabled(false); + this.ruleService.saveRule(this.nodeRef, rule2); + + // Re-try the test now the rule has been re-enabled + NodeRef newNodeRef2 = this.nodeService.createNode( + this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "children"), + ContentModel.TYPE_CONTENT, + getContentProperties()).getChildRef(); + addContentToNode(newNodeRef2); + assertTrue(this.nodeService.hasAspect(newNodeRef2, ContentModel.ASPECT_CONFIGURABLE)); + + } + public void testDisableRule() { this.nodeService.addAspect(this.nodeRef, ContentModel.ASPECT_LOCKABLE, null); @@ -427,15 +466,15 @@ public class RuleServiceCoverageTest extends TestCase getContentProperties()).getChildRef(); addContentToNode(contentToCopy); - // Create the rule and add to folder Map params = new HashMap(1); - params.put("aspect-name", ContentModel.ASPECT_TEMPLATABLE); + params.put("aspect-name", ContentModel.ASPECT_TEMPLATABLE); + Rule rule = createRule( RuleType.INBOUND, AddFeaturesActionExecuter.NAME, params, NoConditionEvaluator.NAME, - null); + null); rule.applyToChildren(true); this.ruleService.saveRule(copyToFolder, rule); @@ -1232,9 +1271,9 @@ public class RuleServiceCoverageTest extends TestCase // Test begins with Map condParamsBegins = new HashMap(1); condParamsBegins.put(ComparePropertyValueEvaluator.PARAM_VALUE, "bob*"); - rule.removeAllActionConditions(); + rule.getAction().removeAllActionConditions(); ActionCondition condition1 = this.actionService.createActionCondition(ComparePropertyValueEvaluator.NAME, condParamsBegins); - rule.addActionCondition(condition1); + rule.getAction().addActionCondition(condition1); this.ruleService.saveRule(this.nodeRef, rule); Map propsx = new HashMap(); propsx.put(ContentModel.PROP_NAME, "mybobbins.doc"); @@ -1264,9 +1303,9 @@ public class RuleServiceCoverageTest extends TestCase // Test ends with Map condParamsEnds = new HashMap(1); condParamsEnds.put(ComparePropertyValueEvaluator.PARAM_VALUE, "*s.doc"); - rule.removeAllActionConditions(); + rule.getAction().removeAllActionConditions(); ActionCondition condition2 = this.actionService.createActionCondition(ComparePropertyValueEvaluator.NAME, condParamsEnds); - rule.addActionCondition(condition2); + rule.getAction().addActionCondition(condition2); this.ruleService.saveRule(this.nodeRef, rule); Map propsa = new HashMap(); propsa.put(ContentModel.PROP_NAME, "bobbins.document"); @@ -1547,4 +1586,62 @@ public class RuleServiceCoverageTest extends TestCase throw new RuntimeException(exception); } } + + public void testAsyncExecutionWithPotentialLoop() + { + if (this.transformerRegistry.getTransformer(MimetypeMap.MIMETYPE_EXCEL, MimetypeMap.MIMETYPE_TEXT_PLAIN) != null) + { + try + { + Map params = new HashMap(1); + params.put(TransformActionExecuter.PARAM_MIME_TYPE, MimetypeMap.MIMETYPE_TEXT_PLAIN); + params.put(TransformActionExecuter.PARAM_DESTINATION_FOLDER, this.nodeRef); + params.put(TransformActionExecuter.PARAM_ASSOC_TYPE_QNAME, ContentModel.ASSOC_CONTAINS); + params.put(TransformActionExecuter.PARAM_ASSOC_QNAME, QName.createQName(TEST_NAMESPACE, "transformed")); + + Rule rule = createRule( + RuleType.INBOUND, + TransformActionExecuter.NAME, + params, + NoConditionEvaluator.NAME, + null); + rule.setExecuteAsynchronously(true); + rule.setTitle("Transform document to text"); + + UserTransaction tx0 = transactionService.getUserTransaction(); + tx0.begin(); + this.ruleService.saveRule(this.nodeRef, rule); + tx0.commit(); + + UserTransaction tx = transactionService.getUserTransaction(); + tx.begin(); + + Map props =new HashMap(1); + props.put(ContentModel.PROP_NAME, "test.xls"); + + // Create the node at the root + NodeRef newNodeRef = this.nodeService.createNode( + this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "origional"), + ContentModel.TYPE_CONTENT, + props).getChildRef(); + + // Set some content on the origional + ContentWriter contentWriter = this.contentService.getWriter(newNodeRef, ContentModel.PROP_CONTENT, true); + contentWriter.setMimetype(MimetypeMap.MIMETYPE_EXCEL); + File testFile = AbstractContentTransformerTest.loadQuickTestFile("xls"); + contentWriter.putContent(testFile); + + tx.commit(); + + // Sleep to ensure work is done b4 execution is canceled + Thread.sleep(10000); + } + catch (Exception exception) + { + throw new RuntimeException(exception); + } + } + } } diff --git a/source/java/org/alfresco/repo/rule/RuleServiceImpl.java b/source/java/org/alfresco/repo/rule/RuleServiceImpl.java index 4cda7352ec..ac31a18ba8 100644 --- a/source/java/org/alfresco/repo/rule/RuleServiceImpl.java +++ b/source/java/org/alfresco/repo/rule/RuleServiceImpl.java @@ -18,7 +18,6 @@ package org.alfresco.repo.rule; import java.io.Serializable; import java.util.ArrayList; -import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -30,6 +29,7 @@ import org.alfresco.repo.action.ActionModel; import org.alfresco.repo.action.RuntimeActionService; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.TransactionListener; +import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.ActionService; import org.alfresco.service.cmr.action.ActionServiceException; import org.alfresco.service.cmr.dictionary.DictionaryService; @@ -40,9 +40,6 @@ import org.alfresco.service.cmr.rule.Rule; import org.alfresco.service.cmr.rule.RuleService; import org.alfresco.service.cmr.rule.RuleServiceException; import org.alfresco.service.cmr.rule.RuleType; -import org.alfresco.service.cmr.search.SearchService; -import org.alfresco.service.namespace.DynamicNamespacePrefixResolver; -import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.RegexQNamePattern; import org.alfresco.util.GUID; @@ -90,11 +87,6 @@ public class RuleServiceImpl implements RuleService, RuntimeRuleService */ private ActionService actionService; - /** - * The search service - */ - private SearchService searchService; - /** * The dictionary service */ @@ -125,7 +117,17 @@ public class RuleServiceImpl implements RuleService, RuntimeRuleService /** * The rule transaction listener */ - private TransactionListener ruleTransactionListener = new RuleTransactionListener(this); + private TransactionListener ruleTransactionListener = new RuleTransactionListener(this); + + /** + * Indicates whether the rules are disabled for the current thread + */ + private ThreadLocal rulesDisabled = new ThreadLocal(); + + /** + * Global flag that indicates whether the + */ + private boolean globalRulesDisabled = false; /** * Set the permission-safe node service @@ -167,16 +169,6 @@ public class RuleServiceImpl implements RuleService, RuntimeRuleService this.runtimeActionService = runtimeActionService; } - /** - * Set the search service - * - * @param searchService the search service - */ - public void setSearchService(SearchService searchService) - { - this.searchService = searchService; - } - /** * Set the dictionary service * @@ -186,6 +178,16 @@ public class RuleServiceImpl implements RuleService, RuntimeRuleService { this.dictionaryService = dictionaryService; } + + /** + * Set the global rules disabled flag + * + * @param rulesDisabled true to disable allr ules, false otherwise + */ + public void setRulesDisabled(boolean rulesDisabled) + { + this.globalRulesDisabled = rulesDisabled; + } /** * Gets the saved rule folder reference @@ -229,6 +231,30 @@ public class RuleServiceImpl implements RuleService, RuntimeRuleService return this.ruleTypes.get(name); } + /** + * @see org.alfresco.service.cmr.rule.RuleService#enableRules() + */ + public void enableRules() + { + this.rulesDisabled.remove(); + } + + /** + * @see org.alfresco.service.cmr.rule.RuleService#disableRules() + */ + public void disableRules() + { + this.rulesDisabled.set(Boolean.TRUE); + } + + /** + * @see org.alfresco.service.cmr.rule.RuleService#isEnabled() + */ + public boolean isEnabled() + { + return (this.globalRulesDisabled == false && this.rulesDisabled.get() == null); + } + /** * @see org.alfresco.service.cmr.rule.RuleService#rulesEnabled(NodeRef) */ @@ -304,7 +330,7 @@ public class RuleServiceImpl implements RuleService, RuntimeRuleService if (this.runtimeNodeService.exists(nodeRef) == true && checkNodeType(nodeRef) == true) { - if (includeInherited == true) + if (includeInherited == true && this.runtimeNodeService.hasAspect(nodeRef, RuleModel.ASPECT_IGNORE_INHERITED_RULES) == false) { // Get any inherited rules for (Rule rule : getInheritedRules(nodeRef, ruleTypeName, null)) @@ -331,7 +357,7 @@ public class RuleServiceImpl implements RuleService, RuntimeRuleService { // Create the rule and add to the list NodeRef ruleNodeRef = ruleChildAssocRef.getChildRef(); - Rule rule = createRule(nodeRef, ruleNodeRef); + Rule rule = getRule(ruleNodeRef); allRules.add(rule); } @@ -339,7 +365,7 @@ public class RuleServiceImpl implements RuleService, RuntimeRuleService for (Rule rule : allRules) { if ((rules.contains(rule) == false) && - (ruleTypeName == null || ruleTypeName.equals(rule.getRuleTypeName()) == true)) + (ruleTypeName == null || rule.getRuleTypes().contains(ruleTypeName) == true)) { rules.add(rule); } @@ -414,131 +440,88 @@ public class RuleServiceImpl implements RuleService, RuntimeRuleService private List getInheritedRules(NodeRef nodeRef, String ruleTypeName, Set visitedNodeRefs) { List inheritedRules = new ArrayList(); - - // Create the visited nodes set if it has not already been created - if (visitedNodeRefs == null) - { - visitedNodeRefs = new HashSet(); - } - - // This check prevents stack over flow when we have a cyclic node graph - if (visitedNodeRefs.contains(nodeRef) == false) - { - visitedNodeRefs.add(nodeRef); - - List allInheritedRules = new ArrayList(); - List parents = this.runtimeNodeService.getParentAssocs(nodeRef); - for (ChildAssociationRef parent : parents) - { - List rules = getRules(parent.getParentRef(), false); - for (Rule rule : rules) - { - // Add is we hanvn't already added and it should be applied to the children - if (rule.isAppliedToChildren() == true && allInheritedRules.contains(rule) == false) - { - allInheritedRules.add(rule); - } - } - - for (Rule rule : getInheritedRules(parent.getParentRef(), ruleTypeName, visitedNodeRefs)) - { - // Ensure that we don't get any rule duplication (don't use a set cos we want to preserve order) - if (allInheritedRules.contains(rule) == false) - { - allInheritedRules.add(rule); - } - } - } - - if (ruleTypeName == null) - { - inheritedRules = allInheritedRules; - } - else - { - // Filter the rule list by rule type - for (Rule rule : allInheritedRules) - { - if (rule.getRuleTypeName().equals(ruleTypeName) == true) - { - inheritedRules.add(rule); - } - } - } - } + + if (this.runtimeNodeService.hasAspect(nodeRef, RuleModel.ASPECT_IGNORE_INHERITED_RULES) == false) + { + // Create the visited nodes set if it has not already been created + if (visitedNodeRefs == null) + { + visitedNodeRefs = new HashSet(); + } + + // This check prevents stack over flow when we have a cyclic node graph + if (visitedNodeRefs.contains(nodeRef) == false) + { + visitedNodeRefs.add(nodeRef); + + List allInheritedRules = new ArrayList(); + List parents = this.runtimeNodeService.getParentAssocs(nodeRef); + for (ChildAssociationRef parent : parents) + { + // Add the inherited rule first + for (Rule rule : getInheritedRules(parent.getParentRef(), ruleTypeName, visitedNodeRefs)) + { + // Ensure that we don't get any rule duplication (don't use a set cos we want to preserve order) + if (allInheritedRules.contains(rule) == false) + { + allInheritedRules.add(rule); + } + } + + List rules = getRules(parent.getParentRef(), false); + for (Rule rule : rules) + { + // Add is we hanvn't already added and it should be applied to the children + if (rule.isAppliedToChildren() == true && allInheritedRules.contains(rule) == false) + { + allInheritedRules.add(rule); + } + } + } + + if (ruleTypeName == null) + { + inheritedRules = allInheritedRules; + } + else + { + // Filter the rule list by rule type + for (Rule rule : allInheritedRules) + { + if (rule.getRuleTypes().contains(ruleTypeName) == true) + { + inheritedRules.add(rule); + } + } + } + } + } return inheritedRules; } - /** - * @see org.alfresco.repo.rule.RuleService#getRule(String) - */ - public Rule getRule(NodeRef nodeRef, String ruleId) - { - Rule rule = null; - - if (this.runtimeNodeService.exists(nodeRef) == true) - { - NodeRef ruleNodeRef = getRuleNodeRefFromId(nodeRef, ruleId); - if (ruleNodeRef != null) - { - rule = createRule(nodeRef, ruleNodeRef); - } - } - - return rule; - } - - /** - * Gets the rule node ref from the action id - * - * @param nodeRef the node reference - * @param actionId the rule id - * @return the rule node reference - */ - private NodeRef getRuleNodeRefFromId(NodeRef nodeRef, String ruleId) - { - NodeRef result = null; - if (this.runtimeNodeService.hasAspect(nodeRef, RuleModel.ASPECT_RULES) == true) - { - NodeRef ruleFolder = getSavedRuleFolderRef(nodeRef); - if (ruleFolder != null) - { - DynamicNamespacePrefixResolver namespacePrefixResolver = new DynamicNamespacePrefixResolver(); - namespacePrefixResolver.registerNamespace(NamespaceService.SYSTEM_MODEL_PREFIX, NamespaceService.SYSTEM_MODEL_1_0_URI); - - List nodeRefs = searchService.selectNodes( - ruleFolder, - "*[@sys:" + ContentModel.PROP_NODE_UUID.getLocalName() + "='" + ruleId + "']", - null, - namespacePrefixResolver, - false); - if (nodeRefs.size() != 0) - { - result = nodeRefs.get(0); - } - } - } - - return result; - } - /** * Create the rule object from the rule node reference * * @param ruleNodeRef the rule node reference * @return the rule */ - private Rule createRule(NodeRef owningNodeRef, NodeRef ruleNodeRef) + public Rule getRule(NodeRef ruleNodeRef) { // Get the rule properties Map props = this.runtimeNodeService.getProperties(ruleNodeRef); // Create the rule - String ruleTypeName = (String)props.get(RuleModel.PROP_RULE_TYPE); - Rule rule = new RuleImpl(ruleNodeRef.getId(), ruleTypeName, owningNodeRef); - - // Set the other rule properties + Rule rule = new Rule(ruleNodeRef); + + // Set the title and description + rule.setTitle((String)props.get(ContentModel.PROP_TITLE)); + rule.setDescription((String)props.get(ContentModel.PROP_DESCRIPTION)); + + // Set the rule types + rule.setRuleTypes((List)props.get(RuleModel.PROP_RULE_TYPE)); + + // Set the applied to children value boolean isAppliedToChildren = false; Boolean value = (Boolean)props.get(RuleModel.PROP_APPLY_TO_CHILDREN); if (value != null) @@ -546,69 +529,141 @@ public class RuleServiceImpl implements RuleService, RuntimeRuleService isAppliedToChildren = value.booleanValue(); } rule.applyToChildren(isAppliedToChildren); + + // Set the execute asynchronously value + boolean executeAsync = false; + Boolean value2 = (Boolean)props.get(RuleModel.PROP_EXECUTE_ASYNC); + if (value2 != null) + { + executeAsync = value2.booleanValue(); + } + rule.setExecuteAsynchronously(executeAsync); + + // Set the disabled value + boolean ruleDisabled = false; + Boolean value3 = (Boolean)props.get(RuleModel.PROP_DISABLED); + if (value3 != null) + { + ruleDisabled = value3.booleanValue(); + } + rule.setRuleDisabled(ruleDisabled); - // Populate the composite action details - runtimeActionService.populateCompositeAction(ruleNodeRef, rule); + // Get the action node reference + List actions = this.nodeService.getChildAssocs(ruleNodeRef, RuleModel.ASSOC_ACTION, RuleModel.ASSOC_ACTION); + if (actions.size() == 0) + { + throw new RuleServiceException("Rule exists without a specified action"); + } + else if (actions.size() > 1) + { + throw new RuleServiceException("Rule exists with more than one specified action"); + } + NodeRef actionNodeRef = actions.get(0).getChildRef(); + // Here we need to create the action from the action node reference + Action action = runtimeActionService.createAction(actionNodeRef); + rule.setAction(action); + return rule; } - /** - * @see org.alfresco.repo.rule.RuleService#createRule(org.alfresco.repo.rule.RuleType) - */ - public Rule createRule(String ruleTypeName) - { - // Create the new rule, giving it a unique rule id - String id = GUID.generate(); - return new RuleImpl(id, ruleTypeName, null); - } - /** * @see org.alfresco.repo.rule.RuleService#saveRule(org.alfresco.repo.ref.NodeRef, org.alfresco.repo.rule.Rule) */ public void saveRule(NodeRef nodeRef, Rule rule) { - if (this.nodeService.exists(nodeRef) == false) - { - throw new RuleServiceException("The node does not exist."); - } - - NodeRef ruleNodeRef = getRuleNodeRefFromId(nodeRef, rule.getId()); - if (ruleNodeRef == null) - { - if (this.nodeService.hasAspect(nodeRef, RuleModel.ASPECT_RULES) == false) - { - // Add the actionable aspect - this.nodeService.addAspect(nodeRef, RuleModel.ASPECT_RULES, null); - } - - Map props = new HashMap(3); - props.put(RuleModel.PROP_RULE_TYPE, rule.getRuleTypeName()); - props.put(ActionModel.PROP_DEFINITION_NAME, rule.getActionDefinitionName()); - props.put(ContentModel.PROP_NODE_UUID, rule.getId()); - - // Create the action node - ruleNodeRef = this.nodeService.createNode( - getSavedRuleFolderRef(nodeRef), - ContentModel.ASSOC_CONTAINS, - QName.createQName(RuleModel.RULE_MODEL_URI, ASSOC_NAME_RULES_PREFIX + GUID.generate()), - RuleModel.TYPE_RULE, - props).getChildRef(); - - // Update the created details - ((RuleImpl)rule).setCreator((String)this.nodeService.getProperty(ruleNodeRef, ContentModel.PROP_CREATOR)); - ((RuleImpl)rule).setCreatedDate((Date)this.nodeService.getProperty(ruleNodeRef, ContentModel.PROP_CREATED)); - } - - // Update the properties of the rule - this.nodeService.setProperty(ruleNodeRef, RuleModel.PROP_APPLY_TO_CHILDREN, rule.isAppliedToChildren()); - - // Save the remainder of the rule as a composite action - runtimeActionService.saveActionImpl(nodeRef, ruleNodeRef, rule); + disableRules(); + try + { + if (this.nodeService.exists(nodeRef) == false) + { + throw new RuleServiceException("The node does not exist."); + } + + NodeRef ruleNodeRef = rule.getNodeRef(); + if (ruleNodeRef == null) + { + if (this.nodeService.hasAspect(nodeRef, RuleModel.ASPECT_RULES) == false) + { + // Add the actionable aspect + this.nodeService.addAspect(nodeRef, RuleModel.ASPECT_RULES, null); + } + + // Create the action node + ruleNodeRef = this.nodeService.createNode( + getSavedRuleFolderRef(nodeRef), + ContentModel.ASSOC_CONTAINS, + QName.createQName(RuleModel.RULE_MODEL_URI, ASSOC_NAME_RULES_PREFIX + GUID.generate()), + RuleModel.TYPE_RULE).getChildRef(); + + // Set the rule node reference and the owning node reference + rule.setNodeRef(ruleNodeRef); + } + + // Update the properties of the rule + this.nodeService.setProperty(ruleNodeRef, ContentModel.PROP_TITLE, rule.getTitle()); + this.nodeService.setProperty(ruleNodeRef, ContentModel.PROP_DESCRIPTION, rule.getDescription()); + this.nodeService.setProperty(ruleNodeRef, RuleModel.PROP_RULE_TYPE, (Serializable)rule.getRuleTypes()); + this.nodeService.setProperty(ruleNodeRef, RuleModel.PROP_APPLY_TO_CHILDREN, rule.isAppliedToChildren()); + this.nodeService.setProperty(ruleNodeRef, RuleModel.PROP_EXECUTE_ASYNC, rule.getExecuteAsynchronously()); + this.nodeService.setProperty(ruleNodeRef, RuleModel.PROP_DISABLED, rule.getRuleDisabled()); + + // Save the rule's action + saveAction(ruleNodeRef, rule); + } + finally + { + enableRules(); + } } /** - * @see org.alfresco.repo.rule.RuleService#removeRule(org.alfresco.repo.ref.NodeRef, org.alfresco.repo.rule.RuleImpl) + * Save the action related to the rule. + * + * @param ruleNodeRef the node reference representing the rule + * @param rule the rule + */ + private void saveAction(NodeRef ruleNodeRef, Rule rule) + { + // Get the action definition from the rule + Action action = rule.getAction(); + if (action == null) + { + throw new RuleServiceException("An action must be specified when defining a rule."); + } + + // Get the current action node reference + NodeRef actionNodeRef = null; + List actions = this.nodeService.getChildAssocs(ruleNodeRef, RuleModel.ASSOC_ACTION, RuleModel.ASSOC_ACTION); + if (actions.size() == 1) + { + // We need to check that the action is the same + actionNodeRef = actions.get(0).getChildRef(); + if (actionNodeRef.getId().equals(action.getId()) == false) + { + // Delete the old action + this.nodeService.deleteNode(actionNodeRef); + actionNodeRef = null; + } + } + else if (actions.size() > 1) + { + throw new RuleServiceException("The rule has become corrupt. More than one action is associated with the rule."); + } + + // Create the new action node reference + if (actionNodeRef == null) + { + actionNodeRef = this.runtimeActionService.createActionNodeRef(action, ruleNodeRef, RuleModel.ASSOC_ACTION, RuleModel.ASSOC_ACTION); + } + + // Update the action node + this.runtimeActionService.saveActionImpl(actionNodeRef, action); + + } + + /** + * @see org.alfresco.repo.rule.RuleService#removeRule(org.alfresco.repo.ref.NodeRef, org.alfresco.service.cmr.rule.Rule) */ public void removeRule(NodeRef nodeRef, Rule rule) { @@ -618,7 +673,7 @@ public class RuleServiceImpl implements RuleService, RuntimeRuleService disableRules(nodeRef); try { - NodeRef ruleNodeRef = getRuleNodeRefFromId(nodeRef, rule.getId()); + NodeRef ruleNodeRef = rule.getNodeRef(); if (ruleNodeRef != null) { this.nodeService.removeChild(getSavedRuleFolderRef(nodeRef), ruleNodeRef); @@ -653,27 +708,34 @@ public class RuleServiceImpl implements RuleService, RuntimeRuleService } } + /** + * @see org.alfresco.repo.rule.RuntimeRuleService#addRulePendingExecution(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.rule.Rule) + */ @SuppressWarnings("unchecked") public void addRulePendingExecution(NodeRef actionableNodeRef, NodeRef actionedUponNodeRef, Rule rule) { addRulePendingExecution(actionableNodeRef, actionedUponNodeRef, rule, false); } + /** + * @see org.alfresco.repo.rule.RuntimeRuleService#addRulePendingExecution(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.rule.Rule, boolean) + */ @SuppressWarnings("unchecked") public void addRulePendingExecution(NodeRef actionableNodeRef, NodeRef actionedUponNodeRef, Rule rule, boolean executeAtEnd) { // First check to see if the node has been disabled - if (this.disabledNodeRefs.contains(rule.getOwningNodeRef()) == false && + if (this.isEnabled() == true && + this.disabledNodeRefs.contains(this.getOwningNodeRef(rule)) == false && this.disabledRules.contains(rule) == false) { PendingRuleData pendingRuleData = new PendingRuleData(actionableNodeRef, actionedUponNodeRef, rule, executeAtEnd); - Set pendingRules = - (Set) AlfrescoTransactionSupport.getResource(KEY_RULES_PENDING); + List pendingRules = + (List) AlfrescoTransactionSupport.getResource(KEY_RULES_PENDING); if (pendingRules == null) { // bind pending rules to the current transaction - pendingRules = new HashSet(); + pendingRules = new ArrayList(); AlfrescoTransactionSupport.bindResource(KEY_RULES_PENDING, pendingRules); // bind the rule transaction listener AlfrescoTransactionSupport.bindListener(this.ruleTransactionListener); @@ -684,14 +746,17 @@ public class RuleServiceImpl implements RuleService, RuntimeRuleService } } - // Prevent hte same rule being executed more than one in the same transaction - pendingRules.add(pendingRuleData); + // Prevent the same rule being executed more than once in the same transaction + if (pendingRules.contains(pendingRuleData) == false) + { + pendingRules.add(pendingRuleData); + } } else { if (logger.isDebugEnabled() == true) { - logger.debug("The rule '" + rule.getTitle() + "' or the node '" + rule.getOwningNodeRef().getId() + "' has been disabled."); + logger.debug("The rule '" + rule.getTitle() + "' or the node '" + this.getOwningNodeRef(rule).getId() + "' has been disabled."); } } } @@ -707,23 +772,16 @@ public class RuleServiceImpl implements RuleService, RuntimeRuleService { logger.debug("Creating the executed rules list"); } - AlfrescoTransactionSupport.bindResource(KEY_RULES_EXECUTED, new HashSet()); - try + if (AlfrescoTransactionSupport.getResource(KEY_RULES_EXECUTED) == null) + { + AlfrescoTransactionSupport.bindResource(KEY_RULES_EXECUTED, new HashSet()); + } + + List executeAtEndRules = new ArrayList(); + executePendingRulesImpl(executeAtEndRules); + for (PendingRuleData data : executeAtEndRules) { - List executeAtEndRules = new ArrayList(); - executePendingRulesImpl(executeAtEndRules); - for (PendingRuleData data : executeAtEndRules) - { - executePendingRule(data); - } - } - finally - { - AlfrescoTransactionSupport.unbindResource(KEY_RULES_EXECUTED); - if (logger.isDebugEnabled() == true) - { - logger.debug("Unbinding resource"); - } + executePendingRule(data); } } @@ -734,8 +792,8 @@ public class RuleServiceImpl implements RuleService, RuntimeRuleService private void executePendingRulesImpl(List executeAtEndRules) { // get the transaction-local rules to execute - Set pendingRules = - (Set) AlfrescoTransactionSupport.getResource(KEY_RULES_PENDING); + List pendingRules = + (List) AlfrescoTransactionSupport.getResource(KEY_RULES_PENDING); // only execute if there are rules present if (pendingRules != null && !pendingRules.isEmpty()) { @@ -776,22 +834,41 @@ public class RuleServiceImpl implements RuleService, RuntimeRuleService if (executedRules == null || canExecuteRule(executedRules, actionedUponNodeRef, rule) == true) { - // Evaluate the condition - if (this.actionService.evaluateAction(rule, actionedUponNodeRef) == true) - { - // Add the rule to the executed rule list - // (do this before this is executed to prevent rules being added to the pending list) - executedRules.add(new ExecutedRuleData(actionedUponNodeRef, rule)); - if (logger.isDebugEnabled() == true) - { - logger.debug(" ... Adding rule (" + rule.getTitle() + ") and nodeRef (" + actionedUponNodeRef.getId() + ") to executed list"); - } - - // Execute the rule - this.actionService.executeAction(rule, actionedUponNodeRef); - } + executeRule(rule, actionedUponNodeRef, executedRules); } } + + /** + * @see org.alfresco.repo.rule.RuntimeRuleService#executeRule(org.alfresco.service.cmr.rule.Rule, org.alfresco.service.cmr.repository.NodeRef, java.util.Set) + */ + public void executeRule(Rule rule, NodeRef actionedUponNodeRef, Set executedRules) + { + // Get the action associated with the rule + Action action = rule.getAction(); + if (action == null) + { + throw new RuleServiceException("Attempting to execute a rule that does not have a rule specified."); + } + + // Evaluate the condition + if (this.actionService.evaluateAction(action, actionedUponNodeRef) == true) + { + if (executedRules != null) + { + // Add the rule to the executed rule list + // (do this before this is executed to prevent rules being added to the pending list) + executedRules.add(new ExecutedRuleData(actionedUponNodeRef, rule)); + if (logger.isDebugEnabled() == true) + { + logger.debug(" ... Adding rule (" + rule.getTitle() + ") and nodeRef (" + actionedUponNodeRef.getId() + ") to executed list"); + } + } + + // Execute the rule + boolean executeAsync = rule.getExecuteAsynchronously(); + this.actionService.executeAction(action, actionedUponNodeRef, true, executeAsync); + } + } /** * Determines whether the rule can be executed @@ -900,7 +977,7 @@ public class RuleServiceImpl implements RuleService, RuntimeRuleService * * @author Roy Wetherall */ - private class ExecutedRuleData + public class ExecutedRuleData { protected NodeRef actionableNodeRef; @@ -1005,4 +1082,70 @@ public class RuleServiceImpl implements RuleService, RuntimeRuleService } } } + + /** + * @see org.alfresco.service.cmr.rule.RuleService#getOwningNodeRef(org.alfresco.service.cmr.rule.Rule) + */ + public NodeRef getOwningNodeRef(Rule rule) + { + NodeRef result = null; + + NodeRef ruleNodeRef = rule.getNodeRef(); + if (ruleNodeRef != null) + { + result = getOwningNodeRefRuleImpl(ruleNodeRef); + } + + return result; + } + + /** + * @param ruleNodeRef + * @return + */ + private NodeRef getOwningNodeRefRuleImpl(NodeRef ruleNodeRef) + { + // Get the system folder parent + NodeRef systemFolder = this.nodeService.getPrimaryParent(ruleNodeRef).getParentRef(); + + // Get the owning node ref + return this.nodeService.getPrimaryParent(systemFolder).getParentRef(); + } + + /** + * @see org.alfresco.service.cmr.rule.RuleService#getOwningNodeRef(org.alfresco.service.cmr.action.Action) + */ + public NodeRef getOwningNodeRef(Action action) + { + NodeRef result = null; + NodeRef actionNodeRef = action.getNodeRef(); + if (actionNodeRef != null) + { + result = getOwningNodeRefActionImpl(actionNodeRef); + } + + return result; + } + + /** + * @param actionNodeRef + */ + private NodeRef getOwningNodeRefActionImpl(NodeRef actionNodeRef) + { + NodeRef result = null; + NodeRef parentNodeRef = this.nodeService.getPrimaryParent(actionNodeRef).getParentRef(); + if (parentNodeRef != null) + { + QName parentType = this.nodeService.getType(parentNodeRef); + if (RuleModel.TYPE_RULE.equals(parentType) == true) + { + result = getOwningNodeRefRuleImpl(parentNodeRef); + } + else if (ActionModel.TYPE_COMPOSITE_ACTION.equals(parentType) == true) + { + result = getOwningNodeRefActionImpl(parentNodeRef); + } + } + return result; + } } diff --git a/source/java/org/alfresco/repo/rule/RuleServiceImplTest.java b/source/java/org/alfresco/repo/rule/RuleServiceImplTest.java index f3216b9d8c..10f34d4820 100644 --- a/source/java/org/alfresco/repo/rule/RuleServiceImplTest.java +++ b/source/java/org/alfresco/repo/rule/RuleServiceImplTest.java @@ -60,17 +60,6 @@ public class RuleServiceImplTest extends BaseRuleTest } } - /** - * Test createRule - */ - public void testCreateRule() - { - Rule newRule = this.ruleService.createRule("ruleType1"); - assertNotNull(newRule); - assertNotNull(newRule.getId()); - assertEquals("ruleType1", newRule.getRuleTypeName()); - } - /** * Test addRule * @@ -78,17 +67,21 @@ public class RuleServiceImplTest extends BaseRuleTest public void testAddRule() { Rule newRule = createTestRule(); - String ruleId = newRule.getId(); - this.ruleService.saveRule(this.nodeRef, newRule); + this.ruleService.saveRule(this.nodeRef, newRule); + assertNotNull(newRule.getNodeRef()); - Rule savedRule = this.ruleService.getRule(this.nodeRef, ruleId); + // Check the owning node reference + assertNotNull(this.ruleService.getOwningNodeRef(newRule)); + assertEquals(this.nodeRef, this.ruleService.getOwningNodeRef(newRule)); + + Rule savedRule = this.ruleService.getRule(newRule.getNodeRef()); assertNotNull(savedRule); assertFalse(savedRule.isAppliedToChildren()); savedRule.applyToChildren(true); this.ruleService.saveRule(this.nodeRef, savedRule); - Rule savedRule2 = this.ruleService.getRule(this.nodeRef, ruleId); + Rule savedRule2 = this.ruleService.getRule(savedRule.getNodeRef()); assertNotNull(savedRule2); assertTrue(savedRule2.isAppliedToChildren()); } @@ -100,9 +93,9 @@ public class RuleServiceImplTest extends BaseRuleTest assertNotNull(rules1); assertEquals(0, rules1.size()); - Rule newRule = this.ruleService.createRule(ruleType.getName()); + Rule newRule = createTestRule(); //this.ruleService.createRule(ruleType.getName()); this.ruleService.saveRule(this.nodeRef, newRule); - Rule newRule2 = this.ruleService.createRule(ruleType.getName()); + Rule newRule2 = createTestRule(); //this.ruleService.createRule(ruleType.getName()); this.ruleService.saveRule(this.nodeRef, newRule2); List rules2 = this.ruleService.getRules(this.nodeRef); @@ -113,8 +106,7 @@ public class RuleServiceImplTest extends BaseRuleTest List rules3 = this.ruleService.getRules(this.nodeRef); assertNotNull(rules3); - assertEquals(0, rules3.size()); - + assertEquals(0, rules3.size()); } /** @@ -145,16 +137,136 @@ public class RuleServiceImplTest extends BaseRuleTest Rule rule = rules.get(0); assertEquals("title", rule.getTitle()); assertEquals("description", rule.getDescription()); - assertNotNull(rule.getCreatedDate()); - assertNotNull(rule.getModifiedDate()); + assertNotNull(this.nodeService.getProperty(rule.getNodeRef(), ContentModel.PROP_CREATED)); + assertNotNull(this.nodeService.getProperty(rule.getNodeRef(), ContentModel.PROP_CREATOR)); // Check that the condition action have been retireved correctly - List conditions = rule.getActionConditions(); + Action action = rule.getAction(); + assertNotNull(action); + List conditions = action.getActionConditions(); assertNotNull(conditions); - assertEquals(1, conditions.size()); - List actions = rule.getActions(); - assertNotNull(actions); - assertEquals(1, actions.size()); + assertEquals(1, conditions.size()); + } + + /** Ensure the rules are retrieved in the correct order **/ + public void testGetRulesOrder() + { + for (int index = 0; index < 10; index++) + { + Rule newRule = createTestRule(true, Integer.toString(index)); + this.ruleService.saveRule(this.nodeRef, newRule); + } + + // Check that they are all returned in the correct order + List rules = this.ruleService.getRules(this.nodeRef); + int index = 0; + for (Rule rule : rules) + { + assertEquals(Integer.toString(index), rule.getTitle()); + index++; + } + + // Create a child node + NodeRef level1 = createNewNode(this.nodeRef); + for (int index2 = 10; index2 < 20; index2++) + { + Rule newRule = createTestRule(true, Integer.toString(index2)); + this.ruleService.saveRule(level1, newRule); + } + + // Check that they are all returned in the correct order + List rules2 = this.ruleService.getRules(level1); + int index2 = 0; + for (Rule rule : rules2) + { + assertEquals(Integer.toString(index2), rule.getTitle()); + index2++; + } + + // Create a child node + NodeRef level2 = createNewNode(level1); + for (int index3 = 20; index3 < 30; index3++) + { + Rule newRule = createTestRule(true, Integer.toString(index3)); + this.ruleService.saveRule(level2, newRule); + } + + // Check that they are all returned in the correct order + List rules3 = this.ruleService.getRules(level2); + int index3 = 0; + for (Rule rule : rules3) + { + //System.out.println(rule.getTitle()); + assertEquals(Integer.toString(index3), rule.getTitle()); + index3++; + } + + // Update a couple of the rules + Rule rule1 = rules3.get(2); + rule1.setDescription("This has been changed"); + this.ruleService.saveRule(this.nodeRef, rule1); + Rule rule2 = rules3.get(12); + rule2.setDescription("This has been changed"); + this.ruleService.saveRule(level1, rule2); + Rule rule3 = rules3.get(22); + rule3.setDescription("This has been changed"); + this.ruleService.saveRule(level2, rule3); + + // Check that they are all returned in the correct order + List rules4 = this.ruleService.getRules(level2); + int index4 = 0; + for (Rule rule : rules4) + { + assertEquals(Integer.toString(index4), rule.getTitle()); + index4++; + } + } + + public void testIgnoreInheritedRules() + { + // Create the nodes and rules + this.ruleService.saveRule(this.nodeRef, createTestRule(true, "rule1")); + this.ruleService.saveRule(this.nodeRef, createTestRule(false, "rule2")); + NodeRef nodeRef1 = createNewNode(this.nodeRef); + this.ruleService.saveRule(nodeRef1, createTestRule(true, "rule3")); + this.ruleService.saveRule(nodeRef1, createTestRule(false, "rule4")); + NodeRef nodeRef2 = createNewNode(nodeRef1); + this.ruleService.saveRule(nodeRef2, createTestRule(true, "rule5")); + this.ruleService.saveRule(nodeRef2, createTestRule(false, "rule6")); + + // Apply the ignore aspect + this.nodeService.addAspect(nodeRef1, RuleModel.ASPECT_IGNORE_INHERITED_RULES, null); + + // Get the rules + List rules1 = this.ruleService.getRules(nodeRef2); + assertNotNull(rules1); + assertEquals(3, rules1.size()); + assertEquals("rule3", rules1.get(0).getTitle()); + assertEquals("rule5", rules1.get(1).getTitle()); + assertEquals("rule6", rules1.get(2).getTitle()); + + // Apply the ignore aspect + this.nodeService.addAspect(nodeRef2, RuleModel.ASPECT_IGNORE_INHERITED_RULES, null); + + // Get the rules + List rules2 = this.ruleService.getRules(nodeRef2); + assertNotNull(rules2); + assertEquals(2, rules2.size()); + assertEquals("rule5", rules2.get(0).getTitle()); + assertEquals("rule6", rules2.get(1).getTitle()); + + // Remove the ignore aspect + this.nodeService.removeAspect(nodeRef1, RuleModel.ASPECT_IGNORE_INHERITED_RULES); + this.nodeService.removeAspect(nodeRef2, RuleModel.ASPECT_IGNORE_INHERITED_RULES); + + // Get the rules + List rules3 = this.ruleService.getRules(nodeRef2); + assertNotNull(rules3); + assertEquals(4, rules3.size()); + assertEquals("rule1", rules3.get(0).getTitle()); + assertEquals("rule3", rules3.get(1).getTitle()); + assertEquals("rule5", rules3.get(2).getTitle()); + assertEquals("rule6", rules3.get(3).getTitle()); } /** @@ -176,13 +288,12 @@ public class RuleServiceImplTest extends BaseRuleTest * @param parent the parent node * @param isActionable indicates whether the node is actionable or not */ - private NodeRef createNewNode(NodeRef parent, boolean isActionable) + private NodeRef createNewNode(NodeRef parent) { - NodeRef newNodeRef = this.nodeService.createNode(parent, + return this.nodeService.createNode(parent, ContentModel.ASSOC_CHILDREN, QName.createQName("{test}testnode"), - ContentModel.TYPE_CONTAINER).getChildRef(); - return newNodeRef; + ContentModel.TYPE_CONTAINER).getChildRef(); } /** @@ -193,21 +304,21 @@ public class RuleServiceImplTest extends BaseRuleTest { // Create the nodes and rules - NodeRef rootWithRules = createNewNode(this.rootNodeRef, true); + NodeRef rootWithRules = createNewNode(this.rootNodeRef); Rule rule1 = createTestRule(); this.ruleService.saveRule(rootWithRules, rule1); Rule rule2 = createTestRule(true); this.ruleService.saveRule(rootWithRules, rule2); - NodeRef nonActionableChild = createNewNode(rootWithRules, false); + NodeRef nonActionableChild = createNewNode(rootWithRules); - NodeRef childWithRules = createNewNode(nonActionableChild, true); + NodeRef childWithRules = createNewNode(nonActionableChild); Rule rule3 = createTestRule(); this.ruleService.saveRule(childWithRules, rule3); Rule rule4 = createTestRule(true); this.ruleService.saveRule(childWithRules, rule4); - NodeRef rootWithRules2 = createNewNode(this.rootNodeRef, true); + NodeRef rootWithRules2 = createNewNode(this.rootNodeRef); this.nodeService.addChild( rootWithRules2, childWithRules, @@ -220,7 +331,7 @@ public class RuleServiceImplTest extends BaseRuleTest // Check that the rules are inherited in the correct way - List allRules = this.ruleService.getRules(childWithRules, true); + List allRules = this.ruleService.getRules(childWithRules); assertNotNull(allRules); assertEquals(4, allRules.size()); assertTrue(allRules.contains(rule2)); @@ -232,7 +343,9 @@ public class RuleServiceImplTest extends BaseRuleTest int count = 0; for (Rule rule : allRules) { - if (rule.getOwningNodeRef() == childWithRules) + NodeRef owningNodeRef = this.ruleService.getOwningNodeRef(rule); + assertNotNull(owningNodeRef); + if (owningNodeRef.equals(childWithRules) == true) { count++; } @@ -439,9 +552,9 @@ public class RuleServiceImplTest extends BaseRuleTest public void testCyclicGraphWithInheritedRules() throws Exception { - NodeRef nodeRef1 = createNewNode(this.rootNodeRef, true); - NodeRef nodeRef2 = createNewNode(nodeRef1, true); - NodeRef nodeRef3 = createNewNode(nodeRef2, true); + NodeRef nodeRef1 = createNewNode(this.rootNodeRef); + NodeRef nodeRef2 = createNewNode(nodeRef1); + NodeRef nodeRef3 = createNewNode(nodeRef2); try { this.nodeService.addChild(nodeRef3, nodeRef1, ContentModel.ASSOC_CHILDREN, QName.createQName("{test}loop")); @@ -487,10 +600,10 @@ public class RuleServiceImplTest extends BaseRuleTest */ public void testRuleDuplication() { - NodeRef nodeRef1 = createNewNode(this.rootNodeRef, true); - NodeRef nodeRef2 = createNewNode(nodeRef1, true); - NodeRef nodeRef3 = createNewNode(nodeRef2, true); - NodeRef nodeRef4 = createNewNode(nodeRef1, true); + NodeRef nodeRef1 = createNewNode(this.rootNodeRef); + NodeRef nodeRef2 = createNewNode(nodeRef1); + NodeRef nodeRef3 = createNewNode(nodeRef2); + NodeRef nodeRef4 = createNewNode(nodeRef1); this.nodeService.addChild(nodeRef4, nodeRef3, ContentModel.ASSOC_CHILDREN, QName.createQName("{test}test")); Rule rule1 = createTestRule(true); @@ -534,7 +647,7 @@ public class RuleServiceImplTest extends BaseRuleTest public void testCyclicAsyncRules() throws Exception { - NodeRef nodeRef = createNewNode(this.rootNodeRef, true); + NodeRef nodeRef = createNewNode(this.rootNodeRef); // Create the first rule @@ -547,17 +660,19 @@ public class RuleServiceImplTest extends BaseRuleTest actionProps.put(ImageTransformActionExecuter.PARAM_ASSOC_TYPE_QNAME, ContentModel.ASSOC_CHILDREN); actionProps.put(ImageTransformActionExecuter.PARAM_ASSOC_QNAME, ContentModel.ASSOC_CHILDREN); - Rule rule = this.ruleService.createRule(this.ruleType.getName()); + Rule rule = new Rule(); + rule.setRuleType(this.ruleType.getName()); rule.setTitle("Convert from *.jpg to *.gif"); rule.setExecuteAsynchronously(true); - ActionCondition actionCondition = this.actionService.createActionCondition(ComparePropertyValueEvaluator.NAME); - actionCondition.setParameterValues(conditionProps); - rule.addActionCondition(actionCondition); - Action action = this.actionService.createAction(ImageTransformActionExecuter.NAME); action.setParameterValues(actionProps); - rule.addAction(action); + + ActionCondition actionCondition = this.actionService.createActionCondition(ComparePropertyValueEvaluator.NAME); + actionCondition.setParameterValues(conditionProps); + action.addActionCondition(actionCondition); + + rule.setAction(action); // Create the next rule @@ -569,17 +684,19 @@ public class RuleServiceImplTest extends BaseRuleTest actionProps2.put(ImageTransformActionExecuter.PARAM_DESTINATION_FOLDER, nodeRef); actionProps2.put(ImageTransformActionExecuter.PARAM_ASSOC_QNAME, ContentModel.ASSOC_CHILDREN); - Rule rule2 = this.ruleService.createRule(this.ruleType.getName()); + Rule rule2 = new Rule(); + rule2.setRuleType(this.ruleType.getName()); rule2.setTitle("Convert from *.gif to *.jpg"); rule2.setExecuteAsynchronously(true); - ActionCondition actionCondition2 = this.actionService.createActionCondition(ComparePropertyValueEvaluator.NAME); - actionCondition2.setParameterValues(conditionProps2); - rule2.addActionCondition(actionCondition2); - Action action2 = this.actionService.createAction(ImageTransformActionExecuter.NAME); action2.setParameterValues(actionProps2); - rule2.addAction(action2); + + ActionCondition actionCondition2 = this.actionService.createActionCondition(ComparePropertyValueEvaluator.NAME); + actionCondition2.setParameterValues(conditionProps2); + action2.addActionCondition(actionCondition2); + + rule2.setAction(action2); // Save the rules this.ruleService.saveRule(nodeRef, rule); diff --git a/source/java/org/alfresco/repo/rule/RuleTypeImpl.java b/source/java/org/alfresco/repo/rule/RuleTypeImpl.java index 9980872562..fdaf1bcf77 100644 --- a/source/java/org/alfresco/repo/rule/RuleTypeImpl.java +++ b/source/java/org/alfresco/repo/rule/RuleTypeImpl.java @@ -21,7 +21,6 @@ import java.util.List; import org.alfresco.i18n.I18NUtil; import org.alfresco.repo.action.CommonResourceAbstractBase; import org.alfresco.repo.rule.ruletrigger.RuleTrigger; -import org.alfresco.service.cmr.action.ActionService; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.rule.Rule; import org.alfresco.service.cmr.rule.RuleService; @@ -41,11 +40,6 @@ public class RuleTypeImpl extends CommonResourceAbstractBase implements RuleType */ private static Log logger = LogFactory.getLog(RuleTypeImpl.class); - /** - * The action service - */ - private ActionService actionService; - /** * The rule service */ @@ -67,16 +61,6 @@ public class RuleTypeImpl extends CommonResourceAbstractBase implements RuleType } } - /** - * Set the action service - * - * @param actionService the action service - */ - public void setActionService(ActionService actionService) - { - this.actionService = actionService; - } - /** * Set the rule service * @@ -114,31 +98,50 @@ public class RuleTypeImpl extends CommonResourceAbstractBase implements RuleType /** * @see org.alfresco.service.cmr.rule.RuleType#triggerRuleType(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.repository.NodeRef) */ - public void triggerRuleType(NodeRef nodeRef, NodeRef actionedUponNodeRef) + public void triggerRuleType(NodeRef nodeRef, NodeRef actionedUponNodeRef, boolean executeRuleImmediately) { - if (this.ruleService.hasRules(nodeRef) == true) + if (this.ruleService.isEnabled() == true) { - List rules = this.ruleService.getRules( - nodeRef, - true, - this.name); - - for (Rule rule : rules) - { + if (this.ruleService.hasRules(nodeRef) == true) + { + List rules = this.ruleService.getRules( + nodeRef, + true, + this.name); + + for (Rule rule : rules) + { + if (logger.isDebugEnabled() == true) + { + NodeRef ruleNodeRef = rule.getNodeRef(); + if (nodeRef != null) + { + logger.debug("Triggering rule " + ruleNodeRef.toString()); + } + } + + // Only queue if the rule is not disabled + if (rule.getRuleDisabled() == false) + { + if (executeRuleImmediately == false) + { + // Queue the rule to be executed at the end of the transaction (but still in the transaction) + ((RuntimeRuleService)this.ruleService).addRulePendingExecution(nodeRef, actionedUponNodeRef, rule); + } + else + { + // Execute the rule now + ((RuntimeRuleService)this.ruleService).executeRule(rule, actionedUponNodeRef, null); + } + } + } + } + else + { if (logger.isDebugEnabled() == true) { - logger.debug("Triggering rule " + rule.getId()); + logger.debug("This node has no rules to trigger."); } - - // Queue the rule to be executed at the end of the transaction (but still in the transaction) - ((RuntimeRuleService)this.ruleService).addRulePendingExecution(nodeRef, actionedUponNodeRef, rule); - } - } - else - { - if (logger.isDebugEnabled() == true) - { - logger.debug("This node has no rules to trigger."); } } } diff --git a/source/java/org/alfresco/repo/rule/RuleTypeImplTest.java b/source/java/org/alfresco/repo/rule/RuleTypeImplTest.java index 120978c00b..e1d44dd068 100644 --- a/source/java/org/alfresco/repo/rule/RuleTypeImplTest.java +++ b/source/java/org/alfresco/repo/rule/RuleTypeImplTest.java @@ -129,7 +129,7 @@ public class RuleTypeImplTest extends BaseSpringTest } @Override - public void triggerRuleType(NodeRef nodeRef, NodeRef actionedUponNodeRef) + public void triggerRuleType(NodeRef nodeRef, NodeRef actionedUponNodeRef, boolean executeRuleImmediately) { this.rulesTriggered = true; } diff --git a/source/java/org/alfresco/repo/rule/RuntimeRuleService.java b/source/java/org/alfresco/repo/rule/RuntimeRuleService.java index bf447bf10d..9cfbb649eb 100644 --- a/source/java/org/alfresco/repo/rule/RuntimeRuleService.java +++ b/source/java/org/alfresco/repo/rule/RuntimeRuleService.java @@ -16,6 +16,9 @@ */ package org.alfresco.repo.rule; +import java.util.Set; + +import org.alfresco.repo.rule.RuleServiceImpl.ExecutedRuleData; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.rule.Rule; import org.alfresco.service.cmr.rule.RuleType; @@ -25,6 +28,8 @@ import org.alfresco.service.cmr.rule.RuleType; */ public interface RuntimeRuleService { + void executeRule(Rule rule, NodeRef actionedUponNodeRef, Set executedRules); + void addRulePendingExecution(NodeRef actionableNodeRef, NodeRef actionedUponNodeRef, Rule rule); void addRulePendingExecution(NodeRef actionableNodeRef, NodeRef actionedUponNodeRef, Rule rule, boolean executeAtEnd); diff --git a/source/java/org/alfresco/repo/rule/ruleModel.xml b/source/java/org/alfresco/repo/rule/ruleModel.xml index 053472cdaf..d41bb8b0d6 100644 --- a/source/java/org/alfresco/repo/rule/ruleModel.xml +++ b/source/java/org/alfresco/repo/rule/ruleModel.xml @@ -9,28 +9,51 @@ + - Rule - act:compositeaction - + sys:base + d:text true + true d:boolean true - + + d:boolean + true + + + d:boolean + true + false + + + + + + act:action + true + false + + + + + cm:titled + cm:auditable + @@ -49,6 +72,10 @@ + + + Ignore Inherited Rules + diff --git a/source/java/org/alfresco/repo/rule/ruletrigger/RuleTriggerAbstractBase.java b/source/java/org/alfresco/repo/rule/ruletrigger/RuleTriggerAbstractBase.java index b684f496b6..bfd983b901 100644 --- a/source/java/org/alfresco/repo/rule/ruletrigger/RuleTriggerAbstractBase.java +++ b/source/java/org/alfresco/repo/rule/ruletrigger/RuleTriggerAbstractBase.java @@ -56,6 +56,12 @@ public abstract class RuleTriggerAbstractBase implements RuleTrigger /** The dictionary service */ protected DictionaryService dictionaryService; + /** + * Indicates whether the rule should be executed immediately or at the end of the transaction. + * By default this is false as all rules are executed at the end of the transaction. + */ + protected boolean executeRuleImmediately = false; + /** * Set the policy component * @@ -96,6 +102,17 @@ public abstract class RuleTriggerAbstractBase implements RuleTrigger this.dictionaryService = dictionaryService; } + /** + * Sets the values that indicates whether the rule should be executed immediately + * or not. + * + * @param executeRuleImmediately true execute the rule immediaely, false otherwise + */ + public void setExecuteRuleImmediately(boolean executeRuleImmediately) + { + this.executeRuleImmediately = executeRuleImmediately; + } + /** * Registration of an interested rule type */ @@ -117,7 +134,7 @@ public abstract class RuleTriggerAbstractBase implements RuleTrigger { for (RuleType ruleType : this.ruleTypes) { - ruleType.triggerRuleType(nodeRef, actionedUponNodeRef); + ruleType.triggerRuleType(nodeRef, actionedUponNodeRef, this.executeRuleImmediately); } } } diff --git a/source/java/org/alfresco/repo/rule/ruletrigger/RuleTriggerTest.java b/source/java/org/alfresco/repo/rule/ruletrigger/RuleTriggerTest.java index b2f7f0fa9f..725fee2833 100644 --- a/source/java/org/alfresco/repo/rule/ruletrigger/RuleTriggerTest.java +++ b/source/java/org/alfresco/repo/rule/ruletrigger/RuleTriggerTest.java @@ -35,7 +35,6 @@ public class RuleTriggerTest extends BaseSpringTest { private static final String ON_CREATE_NODE_TRIGGER = "on-create-node-trigger"; private static final String ON_UPDATE_NODE_TRIGGER = "on-update-node-trigger"; - private static final String ON_DELETE_NODE_TRIGGER = "on-delete-node-trigger"; private static final String ON_CREATE_CHILD_ASSOCIATION_TRIGGER = "on-create-child-association-trigger"; private static final String ON_DELETE_CHILD_ASSOCIATION_TRIGGER = "on-delete-child-association-trigger"; private static final String ON_CREATE_ASSOCIATION_TRIGGER = "on-create-association-trigger"; @@ -93,23 +92,23 @@ public class RuleTriggerTest extends BaseSpringTest assertTrue(ruleType.rulesTriggered); } - public void testOnDeleteNodeTrigger() - { - NodeRef nodeRef = this.nodeService.createNode( - this.rootNodeRef, - ContentModel.ASSOC_CHILDREN, - ContentModel.ASSOC_CHILDREN, - ContentModel.TYPE_CONTAINER).getChildRef(); - - TestRuleType ruleType = createTestRuleType(ON_DELETE_NODE_TRIGGER); - assertFalse(ruleType.rulesTriggered); - - // Try and trigger the type - this.nodeService.deleteNode(nodeRef); - - // Check to see if the rule type has been triggered - assertTrue(ruleType.rulesTriggered); - } +// public void testOnDeleteNodeTrigger() +// { +// NodeRef nodeRef = this.nodeService.createNode( +// this.rootNodeRef, +// ContentModel.ASSOC_CHILDREN, +// ContentModel.ASSOC_CHILDREN, +// ContentModel.TYPE_CONTAINER).getChildRef(); +// +// TestRuleType ruleType = createTestRuleType(ON_DELETE_NODE_TRIGGER); +// assertFalse(ruleType.rulesTriggered); +// +// // Try and trigger the type +// this.nodeService.deleteNode(nodeRef); +// +// // Check to see if the rule type has been triggered +// assertTrue(ruleType.rulesTriggered); +// } public void testOnCreateChildAssociationTrigger() { @@ -287,7 +286,7 @@ public class RuleTriggerTest extends BaseSpringTest return "displayLabel"; } - public void triggerRuleType(NodeRef nodeRef, NodeRef actionedUponNodeRef) + public void triggerRuleType(NodeRef nodeRef, NodeRef actionedUponNodeRef, boolean executeRuleImmediately) { // Indicate that the rules have been triggered this.rulesTriggered = true; diff --git a/source/java/org/alfresco/repo/search/impl/lucene/LuceneAnalyser.java b/source/java/org/alfresco/repo/search/impl/lucene/LuceneAnalyser.java index f59560a93a..d810438117 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/LuceneAnalyser.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/LuceneAnalyser.java @@ -21,6 +21,7 @@ import java.util.HashMap; import java.util.Map; import org.alfresco.repo.search.impl.lucene.analysis.PathAnalyser; +import org.alfresco.repo.search.impl.lucene.analysis.VerbatimAnalyser; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.dictionary.PropertyDefinition; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; @@ -117,8 +118,23 @@ public class LuceneAnalyser extends Analyzer { QName propertyQName = QName.createQName(fieldName.substring(1)); PropertyDefinition propertyDef = dictionaryService.getProperty(propertyQName); - DataTypeDefinition dataType = (propertyDef == null) ? dictionaryService.getDataType(DataTypeDefinition.TEXT) : propertyDef.getDataType(); - analyser = loadAnalyzer(dataType); + if (propertyDef != null) + { + if (propertyDef.isTokenisedInIndex()) + { + DataTypeDefinition dataType = propertyDef.getDataType(); + analyser = loadAnalyzer(dataType); + } + else + { + analyser = new VerbatimAnalyser(); + } + } + else + { + DataTypeDefinition dataType = dictionaryService.getDataType(DataTypeDefinition.TEXT); + analyser = loadAnalyzer(dataType); + } } else { @@ -127,28 +143,31 @@ public class LuceneAnalyser extends Analyzer analysers.put(fieldName, analyser); return analyser; } - + private Analyzer loadAnalyzer(DataTypeDefinition dataType) { String analyserClassName = dataType.getAnalyserClassName(); try { Class clazz = Class.forName(analyserClassName); - Analyzer analyser = (Analyzer)clazz.newInstance(); + Analyzer analyser = (Analyzer) clazz.newInstance(); return analyser; } catch (ClassNotFoundException e) { - throw new RuntimeException("Unable to load analyser for property of type " + dataType.getName() + " using " + analyserClassName); + throw new RuntimeException("Unable to load analyser for property of type " + dataType.getName() + " using " + + analyserClassName); } catch (InstantiationException e) { - throw new RuntimeException("Unable to load analyser for property of type " + dataType.getName() + " using " + analyserClassName); + throw new RuntimeException("Unable to load analyser for property of type " + dataType.getName() + " using " + + analyserClassName); } catch (IllegalAccessException e) { - throw new RuntimeException("Unable to load analyser for property of type " + dataType.getName() + " using " + analyserClassName); + throw new RuntimeException("Unable to load analyser for property of type " + dataType.getName() + " using " + + analyserClassName); } } - + } diff --git a/source/java/org/alfresco/repo/search/impl/lucene/LuceneTest.java b/source/java/org/alfresco/repo/search/impl/lucene/LuceneTest.java index 3d8c37bcbf..a30e255203 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/LuceneTest.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/LuceneTest.java @@ -264,6 +264,7 @@ public class LuceneTest extends TestCase testProperties.put(QName.createQName(TEST_NAMESPACE, "category-ista"), new NodeRef(storeRef, "CategoryId")); testProperties.put(QName.createQName(TEST_NAMESPACE, "noderef-ista"), n1); testProperties.put(QName.createQName(TEST_NAMESPACE, "path-ista"), nodeService.getPath(n3)); + testProperties.put(QName.createQName(TEST_NAMESPACE, "verbatim"), " "); testProperties.put(QName.createQName(TEST_NAMESPACE, "null"), null); testProperties.put(QName.createQName(TEST_NAMESPACE, "list"), new ArrayList()); ArrayList testList = new ArrayList(); @@ -715,7 +716,7 @@ public class LuceneTest extends TestCase } } - public void testSort() throws Exception + public void xtestSort() throws Exception { luceneFTS.pause(); buildBaseIndex(); @@ -1855,6 +1856,19 @@ public class LuceneTest extends TestCase assertNotNull(results.getRow(0).getValue(QName.createQName(TEST_NAMESPACE, "path-ista"))); results.close(); + results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + + escapeQName(QName.createQName(TEST_NAMESPACE, "verbatim")) + ":\" \"", + null, null); + assertEquals(1, results.length()); + assertNotNull(results.getRow(0).getValue(QName.createQName(TEST_NAMESPACE, "verbatim"))); + results.close(); + + results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + + escapeQName(QName.createQName(TEST_NAMESPACE, "verbatim")) + ":\" \"", + null, null); + assertEquals(0, results.length()); + results.close(); + results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "TYPE:\"" + testType.toString() + "\"", null, null); assertEquals(1, results.length()); diff --git a/source/java/org/alfresco/repo/search/impl/lucene/LuceneTest2.java b/source/java/org/alfresco/repo/search/impl/lucene/LuceneTest2.java index 31ff6e3020..92a10d0ea9 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/LuceneTest2.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/LuceneTest2.java @@ -41,6 +41,7 @@ import org.alfresco.repo.dictionary.M2Model; import org.alfresco.repo.node.BaseNodeServiceTest; import org.alfresco.repo.search.QueryParameterDefImpl; import org.alfresco.repo.search.QueryRegisterComponent; +import org.alfresco.repo.search.impl.lucene.analysis.NumericEncoder; import org.alfresco.repo.search.impl.lucene.fts.FullTextSearchIndexer; import org.alfresco.repo.search.results.ChildAssocRefResultSet; import org.alfresco.repo.search.results.DetachedResultSet; @@ -371,6 +372,7 @@ public class LuceneTest2 extends TestCase super(arg0); } + public void test0() throws Exception { luceneFTS.pause(); diff --git a/source/java/org/alfresco/repo/search/impl/lucene/LuceneTest_model.xml b/source/java/org/alfresco/repo/search/impl/lucene/LuceneTest_model.xml index 666297f64e..9d02c13e29 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/LuceneTest_model.xml +++ b/source/java/org/alfresco/repo/search/impl/lucene/LuceneTest_model.xml @@ -219,6 +219,16 @@ true + + d:text + true + false + + true + true + false + + test:testAspect diff --git a/source/java/org/alfresco/repo/search/impl/lucene/analysis/VerbatimAnalyser.java b/source/java/org/alfresco/repo/search/impl/lucene/analysis/VerbatimAnalyser.java new file mode 100644 index 0000000000..817113327f --- /dev/null +++ b/source/java/org/alfresco/repo/search/impl/lucene/analysis/VerbatimAnalyser.java @@ -0,0 +1,22 @@ +package org.alfresco.repo.search.impl.lucene.analysis; + +import java.io.Reader; + +import org.apache.lucene.analysis.Analyzer; +import org.apache.lucene.analysis.TokenStream; + +public class VerbatimAnalyser + +extends Analyzer +{ + + public VerbatimAnalyser() + { + super(); + } + + public TokenStream tokenStream(String fieldName, Reader reader) + { + return new VerbatimTokenFilter(reader); + } +} diff --git a/source/java/org/alfresco/repo/search/impl/lucene/analysis/VerbatimTokenFilter.java b/source/java/org/alfresco/repo/search/impl/lucene/analysis/VerbatimTokenFilter.java new file mode 100644 index 0000000000..b77cee9498 --- /dev/null +++ b/source/java/org/alfresco/repo/search/impl/lucene/analysis/VerbatimTokenFilter.java @@ -0,0 +1,42 @@ +package org.alfresco.repo.search.impl.lucene.analysis; + +import java.io.IOException; +import java.io.Reader; + +import org.apache.lucene.analysis.Token; +import org.apache.lucene.analysis.Tokenizer; + +public class VerbatimTokenFilter extends Tokenizer +{ + boolean readInput = true; + + VerbatimTokenFilter(Reader in) + { + super(in); + } + + @Override + public Token next() throws IOException + { + if (readInput) + { + readInput = false; + StringBuilder buffer = new StringBuilder(); + int current; + char c; + while ((current = input.read()) != -1) + { + c = (char) current; + buffer.append(c); + } + + String token = buffer.toString(); + return new Token(token, 0, token.length() - 1, "VERBATIM"); + } + else + { + return null; + } + } + +} diff --git a/source/java/org/alfresco/repo/search/impl/lucene/index/IndexEntry.java b/source/java/org/alfresco/repo/search/impl/lucene/index/IndexEntry.java index 9f468d3678..e381d4531f 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/index/IndexEntry.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/index/IndexEntry.java @@ -154,6 +154,7 @@ class IndexEntry builder.append("Type=").append(getType()).append(" "); builder.append("Status=").append(getStatus()).append(" "); builder.append("Docs=").append(getDocumentCount()).append(" "); + builder.append("Deletions=").append(getDeletions()).append(" "); return builder.toString(); } 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 6915b63355..0408317939 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 @@ -49,13 +49,11 @@ import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.repo.search.IndexerException; import org.alfresco.repo.search.impl.lucene.FilterIndexReaderByNodeRefs2; import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.util.GUID; import org.apache.log4j.Logger; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.document.Document; -import org.apache.lucene.document.Field; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.MultiReader; @@ -206,6 +204,32 @@ public class IndexInfo private static HashMap indexInfos = new HashMap(); + private int maxDocsForInMemoryMerge = 10000; + + private int writerMinMergeDocs = 1000; + + private int writerMergeFactor = 5; + + private int writerMaxMergeDocs = 1000000; + + private boolean writerUseCompoundFile = true; + + private int mergerMinMergeDocs = 1000; + + private int mergerMergeFactor = 5; + + private int mergerMaxMergeDocs = 1000000; + + private boolean mergerUseCompoundFile = true; + + private int mergerTargetOverlays = 5; + + // TODO: Something to control the maximum number of overlays + + private boolean enableCleanerThread = true; + + private boolean enableMergerThread = true; + static { System.setProperty("disableLuceneLocks", "true"); @@ -283,6 +307,10 @@ public class IndexInfo try { writer = new IndexWriter(oldIndex, new StandardAnalyzer(), false); + writer.setUseCompoundFile(writerUseCompoundFile); + writer.minMergeDocs = writerMinMergeDocs; + writer.mergeFactor = writerMergeFactor; + writer.maxMergeDocs = writerMaxMergeDocs; writer.optimize(); long docs = writer.docCount(); writer.close(); @@ -393,24 +421,32 @@ public class IndexInfo } } // TODO: Add unrecognised folders for deletion. - cleanerThread = new Thread(cleaner); - cleanerThread.setDaemon(true); - cleanerThread.setName("Index cleaner thread"); - //cleanerThread.start(); - mergerThread = new Thread(merger); - mergerThread.setDaemon(true); - mergerThread.setName("Index merger thread"); - //mergerThread.start(); + if (enableCleanerThread) + { + cleanerThread = new Thread(cleaner); + cleanerThread.setDaemon(true); + cleanerThread.setName("Index cleaner thread "+indexDirectory); + cleanerThread.start(); + } + + + if (enableMergerThread) + { + mergerThread = new Thread(merger); + mergerThread.setDaemon(true); + mergerThread.setName("Index merger thread "+indexDirectory); + mergerThread.start(); + } IndexWriter writer; try { writer = new IndexWriter(emptyIndex, new StandardAnalyzer(), true); - writer.setUseCompoundFile(true); - writer.minMergeDocs = 1000; - writer.mergeFactor = 5; - writer.maxMergeDocs = 1000000; + writer.setUseCompoundFile(writerUseCompoundFile); + writer.minMergeDocs = writerMinMergeDocs; + writer.mergeFactor = writerMergeFactor; + writer.maxMergeDocs = writerMaxMergeDocs; } catch (IOException e) { @@ -441,21 +477,28 @@ public class IndexInfo // close index writer if required closeDeltaIndexWriter(id); // Check the index knows about the transaction - File location = ensureDeltaIsRegistered(id); - // Create a dummy index reader to deal with empty indexes and not persist these. - if (IndexReader.indexExists(location)) - { - reader = IndexReader.open(location); - } - else - { - reader = IndexReader.open(emptyIndex); - } + reader = buildAndRegisterDeltaReader(id); indexReaders.put(id, reader); } return reader; } + private IndexReader buildAndRegisterDeltaReader(String id) throws IOException + { + IndexReader reader; + File location = ensureDeltaIsRegistered(id); + // Create a dummy index reader to deal with empty indexes and not persist these. + if (IndexReader.indexExists(location)) + { + reader = IndexReader.open(location); + } + else + { + reader = IndexReader.open(emptyIndex); + } + return reader; + } + /** * The delta information does not need to be saved to disk. * @@ -511,10 +554,10 @@ public class IndexInfo if (!IndexReader.indexExists(location)) { IndexWriter creator = new IndexWriter(location, analyzer, true); - creator.setUseCompoundFile(true); - creator.minMergeDocs = 1000; - creator.mergeFactor = 5; - creator.maxMergeDocs = 1000000; + creator.setUseCompoundFile(writerUseCompoundFile); + creator.minMergeDocs = writerMinMergeDocs; + creator.mergeFactor = writerMergeFactor; + creator.maxMergeDocs = writerMaxMergeDocs; return creator; } return null; @@ -538,10 +581,10 @@ public class IndexInfo if (writer == null) { writer = new IndexWriter(location, analyzer, false); - writer.setUseCompoundFile(true); - writer.minMergeDocs = 1000; - writer.mergeFactor = 5; - writer.maxMergeDocs = 1000000; + writer.setUseCompoundFile(writerUseCompoundFile); + writer.minMergeDocs = writerMinMergeDocs; + writer.mergeFactor = writerMergeFactor; + writer.maxMergeDocs = writerMaxMergeDocs; } indexWriters.put(id, writer); } @@ -789,12 +832,14 @@ public class IndexInfo // TODO: Should use the in memory index but we often end up forcing to disk anyway. // Is it worth it? // luceneIndexer.flushPending(); - IndexReader deltaReader = ReferenceCountingReadOnlyIndexReaderFactory.createReader(id, - getDeltaIndexReader(id)); - ReferenceCounting deltaRefCount = (ReferenceCounting) deltaReader; - deltaRefCount.incrementReferenceCount(); + + IndexReader deltaReader = buildAndRegisterDeltaReader(id); IndexReader reader = new MultiReader(new IndexReader[] { new FilterIndexReaderByNodeRefs2(mainIndexReader, deletions, deleteOnlyNodes), deltaReader }); + reader = ReferenceCountingReadOnlyIndexReaderFactory.createReader("MainReader"+id, reader); + ReferenceCounting refCounting = (ReferenceCounting)reader; + refCounting.incrementReferenceCount(); + refCounting.setInvalidForReuse(); return reader; } finally @@ -1659,7 +1704,18 @@ public class IndexInfo { if (indexIsShared) { + long start = 0l; + if (s_logger.isDebugEnabled()) + { + s_logger.debug(" ... waiting for file lock"); + start = System.nanoTime(); + } fileLock = indexInfoChannel.lock(); + if (s_logger.isDebugEnabled()) + { + long end = System.nanoTime(); + s_logger.debug(" ... got file lock in " + ((end - start)/10e6f) + " ms"); + } if (!checkVersion()) { setStatusFromFile(); @@ -1688,6 +1744,10 @@ public class IndexInfo try { fileLock.release(); + if (s_logger.isDebugEnabled()) + { + s_logger.debug(" ... released file lock"); + } } catch (IOException e) { @@ -1696,6 +1756,11 @@ public class IndexInfo } } + /** + * Helper to print out index information + * + * @param args + */ public static void main(String[] args) { @@ -1721,67 +1786,6 @@ public class IndexInfo } } - // public static void main(String[] args) throws IOException - - // { - // System.setProperty("disableLuceneLocks", "true"); - // - // HashSet deletions = new HashSet(); - // for (int i = 0; i < 0; i++) - // { - // deletions.add(new NodeRef(new StoreRef("woof", "bingle"), GUID.generate())); - // } - // - // int repeat = 100; - // int docs = 1; - // final IndexInfo ii = new IndexInfo(new File("c:\\indexTest")); - // - // long totalTimeA = 0; - // long countA = 0; - // - // while (true) - // { - // long start = System.nanoTime(); - // for (int i = 0; i < repeat; i++) - // { - // String guid = GUID.generate(); - // ii.setStatus(guid, TransactionStatus.ACTIVE, null, null); - // IndexWriter writer = ii.getDeltaIndexWriter(guid, new StandardAnalyzer()); - // - // for (int j = 0; j < docs; j++) - // { - // Document doc = new Document(); - // for (int k = 0; k < 15; k++) - // { - // doc.add(new Field("ID" + k, guid + " " + j + " " + k, false, true, false)); - // } - // writer.addDocument(doc); - // } - // - // ii.closeDeltaIndexWriter(guid); - // ii.setStatus(guid, TransactionStatus.PREPARING, null, null); - // ii.setPreparedState(guid, deletions, docs, false); - // ii.getDeletions(guid); - // ii.setStatus(guid, TransactionStatus.PREPARED, null, null); - // ii.setStatus(guid, TransactionStatus.COMMITTING, null, null); - // ii.setStatus(guid, TransactionStatus.COMMITTED, null, null); - // for (int j = 0; j < 0; j++) - // { - // ii.getMainIndexReferenceCountingReadOnlyIndexReader(); - // } - // } - // - // long end = System.nanoTime(); - // - // totalTimeA += (end - start); - // countA += repeat; - // float average = countA * 1000000000f / totalTimeA; - // - // System.out.println("Repeated " - // + repeat + " in " + ((end - start) / 1000000000.0) + " average = " + average); - // } - // } - /** * Clean up support. * @@ -1796,6 +1800,7 @@ public class IndexInfo while (runnable) { String id = null; + HashSet fails = new HashSet(); while ((id = deleteQueue.poll()) != null) { if (s_logger.isDebugEnabled()) @@ -1811,9 +1816,10 @@ public class IndexInfo s_logger.debug("DELETE FAILED"); } // try again later - deleteQueue.add(id); + fails.add(id); } } + deleteQueue.addAll(fails); synchronized (this) { try @@ -1977,7 +1983,7 @@ public class IndexInfo if (!mergingIndexes && !applyingDeletions) { - if ((indexes > 5) || (deltas > 5)) + if ((indexes > mergerMergeFactor) || (deltas > mergerTargetOverlays)) { if (indexes > deltas) { @@ -2331,7 +2337,7 @@ public class IndexInfo } } - int position = findMergeIndex(1, 1000000, 5, mergeList); + int position = findMergeIndex(1, mergerMaxMergeDocs, mergerMergeFactor, mergeList); String firstMergeId = mergeList.get(position).getName(); long count = 0; @@ -2415,7 +2421,7 @@ public class IndexInfo else if (entry.getStatus() == TransactionStatus.MERGE_TARGET) { outputLocation = location; - if (docCount < 10000) + if (docCount < maxDocsForInMemoryMerge) { ramDirectory = new RAMDirectory(); writer = new IndexWriter(ramDirectory, new StandardAnalyzer(), true); @@ -2423,11 +2429,12 @@ public class IndexInfo else { writer = new IndexWriter(location, new StandardAnalyzer(), true); + } - writer.setUseCompoundFile(true); - writer.minMergeDocs = 1000; - writer.mergeFactor = 5; - writer.maxMergeDocs = 1000000; + writer.setUseCompoundFile(mergerUseCompoundFile); + writer.minMergeDocs = mergerMinMergeDocs; + writer.mergeFactor = mergerMergeFactor; + writer.maxMergeDocs = mergerMaxMergeDocs; } } writer.addIndexes(readers); @@ -2523,17 +2530,18 @@ public class IndexInfo indexEntries.remove(id); deleteQueue.add(id); } - synchronized (cleaner) - { - cleaner.notify(); - } - + dumpInfo(); writeStatus(); clearOldReaders(); + synchronized (cleaner) + { + cleaner.notify(); + } + return null; } @@ -2601,10 +2609,19 @@ public class IndexInfo private void getWriteLock() { + String threadName = null; + long start = 0l; + if (s_logger.isDebugEnabled()) + { + threadName = Thread.currentThread().getName(); + s_logger.debug("Waiting for WRITE lock - " + threadName); + start = System.nanoTime(); + } readWriteLock.writeLock().lock(); if (s_logger.isDebugEnabled()) { - s_logger.debug("GOT WRITE LOCK - " + Thread.currentThread().getName()); + long end = System.nanoTime(); + s_logger.debug("...GOT WRITE LOCK - " + threadName + " - in " + ((end - start)/10e6f) + " ms"); } } @@ -2612,17 +2629,26 @@ public class IndexInfo { if (s_logger.isDebugEnabled()) { - s_logger.debug("RELEASES WRITE LOCK - " + Thread.currentThread().getName()); + s_logger.debug("RELEASED WRITE LOCK - " + Thread.currentThread().getName()); } readWriteLock.writeLock().unlock(); } private void getReadLock() { + String threadName = null; + long start = 0l; + if (s_logger.isDebugEnabled()) + { + threadName = Thread.currentThread().getName(); + s_logger.debug("Waiting for READ lock - " + threadName); + start = System.nanoTime(); + } readWriteLock.readLock().lock(); if (s_logger.isDebugEnabled()) { - s_logger.debug("GOT READ LOCK - " + Thread.currentThread().getName()); + long end = System.nanoTime(); + s_logger.debug("...GOT READ LOCK - " + threadName + " - in " + ((end - start)/10e6f) + " ms"); } } @@ -2630,7 +2656,7 @@ public class IndexInfo { if (s_logger.isDebugEnabled()) { - s_logger.debug("RELEASES READ LOCK - " + Thread.currentThread().getName()); + s_logger.debug("RELEASED READ LOCK - " + Thread.currentThread().getName()); } readWriteLock.readLock().unlock(); } @@ -2639,4 +2665,136 @@ public class IndexInfo { return indexDirectory.toString(); } + + public boolean isEnableCleanerThread() + { + return enableCleanerThread; + } + + public void setEnableCleanerThread(boolean enableCleanerThread) + { + this.enableCleanerThread = enableCleanerThread; + } + + public boolean isEnableMergerThread() + { + return enableMergerThread; + } + + public void setEnableMergerThread(boolean enableMergerThread) + { + this.enableMergerThread = enableMergerThread; + } + + public boolean isIndexIsShared() + { + return indexIsShared; + } + + public void setIndexIsShared(boolean indexIsShared) + { + this.indexIsShared = indexIsShared; + } + + public int getMaxDocsForInMemoryMerge() + { + return maxDocsForInMemoryMerge; + } + + public void setMaxDocsForInMemoryMerge(int maxDocsForInMemoryMerge) + { + this.maxDocsForInMemoryMerge = maxDocsForInMemoryMerge; + } + + public int getMergerMaxMergeDocs() + { + return mergerMaxMergeDocs; + } + + public void setMergerMaxMergeDocs(int mergerMaxMergeDocs) + { + this.mergerMaxMergeDocs = mergerMaxMergeDocs; + } + + public int getMergerMergeFactor() + { + return mergerMergeFactor; + } + + public void setMergerMergeFactor(int mergerMergeFactor) + { + this.mergerMergeFactor = mergerMergeFactor; + } + + public int getMergerMinMergeDocs() + { + return mergerMinMergeDocs; + } + + public void setMergerMinMergeDocs(int mergerMinMergeDocs) + { + this.mergerMinMergeDocs = mergerMinMergeDocs; + } + + public int getMergerTargetOverlays() + { + return mergerTargetOverlays; + } + + public void setMergerTargetOverlays(int mergerTargetOverlays) + { + this.mergerTargetOverlays = mergerTargetOverlays; + } + + public boolean isMergerUseCompoundFile() + { + return mergerUseCompoundFile; + } + + public void setMergerUseCompoundFile(boolean mergerUseCompoundFile) + { + this.mergerUseCompoundFile = mergerUseCompoundFile; + } + + public int getWriterMaxMergeDocs() + { + return writerMaxMergeDocs; + } + + public void setWriterMaxMergeDocs(int writerMaxMergeDocs) + { + this.writerMaxMergeDocs = writerMaxMergeDocs; + } + + public int getWriterMergeFactor() + { + return writerMergeFactor; + } + + public void setWriterMergeFactor(int writerMergeFactor) + { + this.writerMergeFactor = writerMergeFactor; + } + + public int getWriterMinMergeDocs() + { + return writerMinMergeDocs; + } + + public void setWriterMinMergeDocs(int writerMinMergeDocs) + { + this.writerMinMergeDocs = writerMinMergeDocs; + } + + public boolean isWriterUseCompoundFile() + { + return writerUseCompoundFile; + } + + public void setWriterUseCompoundFile(boolean writerUseCompoundFile) + { + this.writerUseCompoundFile = writerUseCompoundFile; + } + + } diff --git a/source/java/org/alfresco/repo/search/impl/lucene/index/IndexInfoTest.java b/source/java/org/alfresco/repo/search/impl/lucene/index/IndexInfoTest.java index eefa22a92d..3770a4f39e 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/index/IndexInfoTest.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/index/IndexInfoTest.java @@ -207,6 +207,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " writer.addDocument(doc); ii.closeDeltaIndexWriter(guid); + ii.setStatus(guid, TransactionStatus.PREPARING, null, null); ii.setPreparedState(guid, new HashSet(), 1, false); ii.getDeletions(guid); diff --git a/source/java/org/alfresco/repo/search/impl/lucene/index/ReferenceCountingReadOnlyIndexReaderFactory.java b/source/java/org/alfresco/repo/search/impl/lucene/index/ReferenceCountingReadOnlyIndexReaderFactory.java index 5c95d2645c..e6e2e03147 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/index/ReferenceCountingReadOnlyIndexReaderFactory.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/index/ReferenceCountingReadOnlyIndexReaderFactory.java @@ -32,16 +32,17 @@ public class ReferenceCountingReadOnlyIndexReaderFactory public static class ReferenceCountingReadOnlyIndexReader extends FilterIndexReader implements ReferenceCounting { private static Logger s_logger = Logger.getLogger(ReferenceCountingReadOnlyIndexReader.class); - - + private static final long serialVersionUID = 7693185658022810428L; String id; - + int refCount = 0; boolean invalidForReuse = false; + boolean allowsDeletions; + ReferenceCountingReadOnlyIndexReader(String id, IndexReader indexReader) { super(indexReader); @@ -51,18 +52,20 @@ public class ReferenceCountingReadOnlyIndexReaderFactory public synchronized void incrementReferenceCount() { refCount++; - if(s_logger.isDebugEnabled()) + if (s_logger.isDebugEnabled()) { - s_logger.debug(Thread.currentThread().getName()+ ": Reader "+id+ " - increment - ref count is "+refCount); + s_logger.debug(Thread.currentThread().getName() + + ": Reader " + id + " - increment - ref count is " + refCount); } } public synchronized void decrementReferenceCount() throws IOException { refCount--; - if(s_logger.isDebugEnabled()) + if (s_logger.isDebugEnabled()) { - s_logger.debug(Thread.currentThread().getName()+ ": Reader "+id+ " - decrement - ref count is "+refCount); + s_logger.debug(Thread.currentThread().getName() + + ": Reader " + id + " - decrement - ref count is " + refCount); } closeIfRequired(); } @@ -71,17 +74,19 @@ public class ReferenceCountingReadOnlyIndexReaderFactory { if ((refCount == 0) && invalidForReuse) { - if(s_logger.isDebugEnabled()) + if (s_logger.isDebugEnabled()) { - s_logger.debug(Thread.currentThread().getName()+ ": Reader "+id+ " closed."); + s_logger.debug(Thread.currentThread().getName() + ": Reader " + id + " closed."); } in.close(); } else { - if(s_logger.isDebugEnabled()) + if (s_logger.isDebugEnabled()) { - s_logger.debug(Thread.currentThread().getName()+ ": Reader "+id+ " still open .... ref = "+refCount+" invalidForReuse = "+invalidForReuse); + s_logger.debug(Thread.currentThread().getName() + + ": Reader " + id + " still open .... ref = " + refCount + " invalidForReuse = " + + invalidForReuse); } } } @@ -94,9 +99,9 @@ public class ReferenceCountingReadOnlyIndexReaderFactory public synchronized void setInvalidForReuse() throws IOException { invalidForReuse = true; - if(s_logger.isDebugEnabled()) + if (s_logger.isDebugEnabled()) { - s_logger.debug(Thread.currentThread().getName()+ ": Reader "+id+ " set invalid for reuse"); + s_logger.debug(Thread.currentThread().getName() + ": Reader " + id + " set invalid for reuse"); } closeIfRequired(); } @@ -104,9 +109,9 @@ public class ReferenceCountingReadOnlyIndexReaderFactory @Override protected void doClose() throws IOException { - if(s_logger.isDebugEnabled()) + if (s_logger.isDebugEnabled()) { - s_logger.debug(Thread.currentThread().getName()+ ": Reader "+id+ " closing"); + s_logger.debug(Thread.currentThread().getName() + ": Reader " + id + " closing"); } decrementReferenceCount(); } @@ -116,7 +121,6 @@ public class ReferenceCountingReadOnlyIndexReaderFactory { throw new UnsupportedOperationException("Delete is not supported by read only index readers"); } - - + } } diff --git a/source/java/org/alfresco/repo/security/authentication/AbstractAuthenticationComponent.java b/source/java/org/alfresco/repo/security/authentication/AbstractAuthenticationComponent.java index b16324a41d..a396d535fe 100644 --- a/source/java/org/alfresco/repo/security/authentication/AbstractAuthenticationComponent.java +++ b/source/java/org/alfresco/repo/security/authentication/AbstractAuthenticationComponent.java @@ -180,11 +180,14 @@ public abstract class AbstractAuthenticationComponent implements AuthenticationC */ private String getUserName(Authentication authentication) { - String username = authentication.getPrincipal().toString(); - + String username; if (authentication.getPrincipal() instanceof UserDetails) { - username = ((UserDetails) authentication.getPrincipal()).getUsername(); + username = ((UserDetails)authentication.getPrincipal()).getUsername(); + } + else + { + username = authentication.getPrincipal().toString(); } return username; diff --git a/source/java/org/alfresco/repo/security/authentication/AuthenticationTest.java b/source/java/org/alfresco/repo/security/authentication/AuthenticationTest.java index d7ae296206..e307338a4a 100644 --- a/source/java/org/alfresco/repo/security/authentication/AuthenticationTest.java +++ b/source/java/org/alfresco/repo/security/authentication/AuthenticationTest.java @@ -39,7 +39,6 @@ import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken; import net.sf.acegisecurity.providers.dao.SaltSource; import org.alfresco.model.ContentModel; -import org.alfresco.repo.security.permissions.PermissionServiceSPI; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.repository.NodeRef; @@ -89,8 +88,6 @@ public class AuthenticationTest extends TestCase private AuthenticationComponent authenticationComponent; - private PermissionServiceSPI permissionServiceSPI; - private UserTransaction userTransaction; private AuthenticationComponent authenticationComponentImpl; @@ -117,7 +114,8 @@ public class AuthenticationTest extends TestCase pubAuthenticationService = (AuthenticationService) ctx.getBean("AuthenticationService"); authenticationComponent = (AuthenticationComponent) ctx.getBean("authenticationComponent"); authenticationComponentImpl = (AuthenticationComponent) ctx.getBean("authenticationComponentImpl"); - permissionServiceSPI = (PermissionServiceSPI) ctx.getBean("permissionService"); + // permissionServiceSPI = (PermissionServiceSPI) + // ctx.getBean("permissionService"); dao = (MutableAuthenticationDao) ctx.getBean("alfDaoImpl"); authenticationManager = (AuthenticationManager) ctx.getBean("authenticationManager"); @@ -181,7 +179,6 @@ public class AuthenticationTest extends TestCase public void xtestScalability() { long create = 0; - long count = 0; long start; long end; @@ -207,6 +204,49 @@ public class AuthenticationTest extends TestCase authenticationComponent.clearCurrentSecurityContext(); } + public void c() + { + try + { + authenticationService.authenticate("", "".toCharArray()); + } + catch (AuthenticationException e) + { + // Expected + } + } + + public void testCreateUsers() + { + authenticationService.createAuthentication("GUEST", "".toCharArray()); + authenticationService.authenticate("GUEST", "".toCharArray()); + // Guest is reported as lower case and the authentication basically + // ignored at the moment + assertEquals("guest", authenticationService.getCurrentUserName()); + + authenticationService.createAuthentication("Andy", "".toCharArray()); + authenticationService.authenticate("Andy", "".toCharArray()); + assertEquals("Andy", authenticationService.getCurrentUserName()); + + authenticationService.createAuthentication("Mr.Woof.Banana@chocolate.chip.cookie.com", "".toCharArray()); + authenticationService.authenticate("Mr.Woof.Banana@chocolate.chip.cookie.com", "".toCharArray()); + assertEquals("Mr.Woof.Banana@chocolate.chip.cookie.com", authenticationService.getCurrentUserName()); + + authenticationService.createAuthentication("Andy_Woof/Domain", "".toCharArray()); + authenticationService.authenticate("Andy_Woof/Domain", "".toCharArray()); + assertEquals("Andy_Woof/Domain", authenticationService.getCurrentUserName()); + + authenticationService.createAuthentication("Andy_ Woof/Domain", "".toCharArray()); + authenticationService.authenticate("Andy_ Woof/Domain", "".toCharArray()); + assertEquals("Andy_ Woof/Domain", authenticationService.getCurrentUserName()); + + + authenticationService.createAuthentication("Andy `\u00ac\u00a6!\u00a3$%^&*()-_=+\t\n\u0000[]{};'#:@~,./<>?\\|", "".toCharArray()); + authenticationService.authenticate("Andy `\u00ac\u00a6!\u00a3$%^&*()-_=+\t\n\u0000[]{};'#:@~,./<>?\\|", "".toCharArray()); + assertEquals("Andy `\u00ac\u00a6!\u00a3$%^&*()-_=+\t\n\u0000[]{};'#:@~,./<>?\\|", authenticationService.getCurrentUserName()); + + } + public void testCreateAndyUserAndOtherCRUD() throws NoSuchAlgorithmException, UnsupportedEncodingException { RepositoryAuthenticationDao dao = new RepositoryAuthenticationDao(); @@ -225,7 +265,7 @@ public class AuthenticationTest extends TestCase UserDetails AndyDetails = (UserDetails) dao.loadUserByUsername("Andy"); assertNotNull(AndyDetails); - assertEquals(dao.getUserNamesAreCaseSensitive() ? "Andy" : "andy", AndyDetails.getUsername()); + assertEquals("Andy", AndyDetails.getUsername()); // assertNotNull(dao.getSalt(AndyDetails)); assertTrue(AndyDetails.isAccountNonExpired()); assertTrue(AndyDetails.isAccountNonLocked()); @@ -240,7 +280,7 @@ public class AuthenticationTest extends TestCase dao.updateUser("Andy", "carrot".toCharArray()); UserDetails newDetails = (UserDetails) dao.loadUserByUsername("Andy"); assertNotNull(newDetails); - assertEquals(dao.getUserNamesAreCaseSensitive() ? "Andy" : "andy", newDetails.getUsername()); + assertEquals("Andy", newDetails.getUsername()); // assertNotNull(dao.getSalt(newDetails)); assertTrue(newDetails.isAccountNonExpired()); assertTrue(newDetails.isAccountNonLocked()); @@ -624,7 +664,7 @@ public class AuthenticationTest extends TestCase authenticationService.authenticate("Andy", "auth1".toCharArray()); // assert the user is authenticated - assertEquals(dao.getUserNamesAreCaseSensitive() ? "Andy" : "andy", authenticationService.getCurrentUserName()); + assertEquals("Andy", authenticationService.getCurrentUserName()); // delete the user authentication object authenticationService.clearCurrentSecurityContext(); @@ -660,7 +700,7 @@ public class AuthenticationTest extends TestCase authenticationService.authenticate("Andy", "auth1".toCharArray()); // assert the user is authenticated - assertEquals(dao.getUserNamesAreCaseSensitive() ? "Andy" : "andy", authenticationService.getCurrentUserName()); + assertEquals("Andy", authenticationService.getCurrentUserName()); // delete the user authentication object authenticationService.clearCurrentSecurityContext(); @@ -696,7 +736,7 @@ public class AuthenticationTest extends TestCase authenticationService.authenticate("Andy", "auth1".toCharArray()); // assert the user is authenticated - assertEquals(dao.getUserNamesAreCaseSensitive() ? "Andy" : "andy", authenticationService.getCurrentUserName()); + assertEquals("Andy", authenticationService.getCurrentUserName()); // delete the user authentication object authenticationService.clearCurrentSecurityContext(); @@ -742,7 +782,7 @@ public class AuthenticationTest extends TestCase authenticationService.authenticate("Andy", "auth1".toCharArray()); // assert the user is authenticated - assertEquals(dao.getUserNamesAreCaseSensitive() ? "Andy" : "andy", authenticationService.getCurrentUserName()); + assertEquals("Andy", authenticationService.getCurrentUserName()); // delete the user authentication object authenticationService.clearCurrentSecurityContext(); @@ -798,7 +838,7 @@ public class AuthenticationTest extends TestCase authenticationService.authenticate("Andy", "auth1".toCharArray()); // assert the user is authenticated - assertEquals(dao.getUserNamesAreCaseSensitive() ? "Andy" : "andy", authenticationService.getCurrentUserName()); + assertEquals("Andy", authenticationService.getCurrentUserName()); // delete the user authentication object authenticationService.clearCurrentSecurityContext(); @@ -857,7 +897,7 @@ public class AuthenticationTest extends TestCase authenticationService.authenticate("Andy", "auth1".toCharArray()); // assert the user is authenticated - assertEquals(dao.getUserNamesAreCaseSensitive() ? "Andy" : "andy", authenticationService.getCurrentUserName()); + assertEquals("Andy", authenticationService.getCurrentUserName()); // delete the user authentication object authenticationService.clearCurrentSecurityContext(); @@ -918,7 +958,7 @@ public class AuthenticationTest extends TestCase pubAuthenticationService.authenticate("Andy", "auth1".toCharArray()); // assert the user is authenticated - assertEquals(dao.getUserNamesAreCaseSensitive() ? "Andy" : "andy", authenticationService.getCurrentUserName()); + assertEquals("Andy", authenticationService.getCurrentUserName()); // delete the user authentication object pubAuthenticationService.clearCurrentSecurityContext(); @@ -966,7 +1006,7 @@ public class AuthenticationTest extends TestCase pubAuthenticationService.authenticate("Andy", "auth1".toCharArray()); // assert the user is authenticated - assertEquals(dao.getUserNamesAreCaseSensitive() ? "Andy" : "andy", authenticationService.getCurrentUserName()); + assertEquals("Andy", authenticationService.getCurrentUserName()); // delete the user authentication object pubAuthenticationService.clearCurrentSecurityContext(); @@ -1013,7 +1053,7 @@ public class AuthenticationTest extends TestCase pubAuthenticationService.authenticate("Andy", "auth1".toCharArray()); // assert the user is authenticated - assertEquals(dao.getUserNamesAreCaseSensitive() ? "Andy" : "andy", authenticationService.getCurrentUserName()); + assertEquals("Andy", authenticationService.getCurrentUserName()); // delete the user authentication object pubAuthenticationService.clearCurrentSecurityContext(); @@ -1085,7 +1125,7 @@ public class AuthenticationTest extends TestCase pubAuthenticationService.authenticate("Andy", "auth1".toCharArray()); // assert the user is authenticated - assertEquals(dao.getUserNamesAreCaseSensitive() ? "Andy" : "andy", authenticationService.getCurrentUserName()); + assertEquals("Andy", authenticationService.getCurrentUserName()); // delete the user authentication object pubAuthenticationService.clearCurrentSecurityContext(); @@ -1114,7 +1154,7 @@ public class AuthenticationTest extends TestCase pubAuthenticationService.invalidateTicket(ticket); } - + public void testPubAuthenticationService0() { // pubAuthenticationService.authenticateAsGuest(); @@ -1145,7 +1185,7 @@ public class AuthenticationTest extends TestCase pubAuthenticationService.authenticate("Andy", "auth1".toCharArray()); // assert the user is authenticated - assertEquals(dao.getUserNamesAreCaseSensitive() ? "Andy" : "andy", authenticationService.getCurrentUserName()); + assertEquals("Andy", authenticationService.getCurrentUserName()); // delete the user authentication object pubAuthenticationService.clearCurrentSecurityContext(); @@ -1172,21 +1212,21 @@ public class AuthenticationTest extends TestCase // destroy the ticket instance pubAuthenticationService.invalidateTicket(ticket); - + authenticationComponent.clearCurrentSecurityContext(); - + pubAuthenticationService.authenticate("Andy", "auth3".toCharArray()); pubAuthenticationService.updateAuthentication("Andy", "auth3".toCharArray(), "auth4".toCharArray()); pubAuthenticationService.authenticate("Andy", "auth4".toCharArray()); - + try { - pubAuthenticationService.updateAuthentication("Andy", "auth3".toCharArray(), "auth4".toCharArray()); - fail("Should not be able to update"); + pubAuthenticationService.updateAuthentication("Andy", "auth3".toCharArray(), "auth4".toCharArray()); + fail("Should not be able to update"); } - catch(AuthenticationException ae) + catch (AuthenticationException ae) { - + } } @@ -1202,7 +1242,7 @@ public class AuthenticationTest extends TestCase authenticationService.createAuthentication("Andy", "auth1".toCharArray()); authenticationComponent.setCurrentUser("Andy"); - assertEquals(dao.getUserNamesAreCaseSensitive() ? "Andy" : "andy", authenticationService.getCurrentUserName()); + assertEquals("Andy", authenticationService.getCurrentUserName()); // authenticationService.deleteAuthentication("andy"); } diff --git a/source/java/org/alfresco/repo/security/authentication/DefaultMutableAuthenticationDao.java b/source/java/org/alfresco/repo/security/authentication/DefaultMutableAuthenticationDao.java index 55fbf2d4cc..553c548ec2 100644 --- a/source/java/org/alfresco/repo/security/authentication/DefaultMutableAuthenticationDao.java +++ b/source/java/org/alfresco/repo/security/authentication/DefaultMutableAuthenticationDao.java @@ -22,7 +22,6 @@ import net.sf.acegisecurity.UserDetails; import net.sf.acegisecurity.providers.dao.UsernameNotFoundException; import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.service.cmr.repository.StoreRef; import org.springframework.dao.DataAccessException; /** @@ -246,16 +245,6 @@ public class DefaultMutableAuthenticationDao implements MutableAuthenticationDao { throw new AlfrescoRuntimeException("Not implemented"); } - - /** - * Are user names case sensitive? - * - * @return - */ - public boolean getUserNamesAreCaseSensitive() - { - throw new AlfrescoRuntimeException("Not implemented"); - } /** * Return the user details for the specified user diff --git a/source/java/org/alfresco/repo/security/authentication/MutableAuthenticationDao.java b/source/java/org/alfresco/repo/security/authentication/MutableAuthenticationDao.java index 22389bbc4a..ca94278a41 100644 --- a/source/java/org/alfresco/repo/security/authentication/MutableAuthenticationDao.java +++ b/source/java/org/alfresco/repo/security/authentication/MutableAuthenticationDao.java @@ -184,11 +184,4 @@ public interface MutableAuthenticationDao extends AuthenticationDao, SaltSource */ public String getMD4HashedPassword(String userName); - /** - * Are user names case sensitive? - * - * @return - */ - public boolean getUserNamesAreCaseSensitive(); - } diff --git a/source/java/org/alfresco/repo/security/authentication/RepositoryAuthenticationDao.java b/source/java/org/alfresco/repo/security/authentication/RepositoryAuthenticationDao.java index d9a544d25d..1f9dc60d7a 100644 --- a/source/java/org/alfresco/repo/security/authentication/RepositoryAuthenticationDao.java +++ b/source/java/org/alfresco/repo/security/authentication/RepositoryAuthenticationDao.java @@ -31,6 +31,7 @@ import net.sf.acegisecurity.providers.encoding.PasswordEncoder; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; +import org.alfresco.repo.search.impl.lucene.LuceneQueryParser; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; @@ -54,6 +55,7 @@ public class RepositoryAuthenticationDao implements MutableAuthenticationDao private NamespacePrefixResolver namespacePrefixResolver; + @SuppressWarnings("unused") private DictionaryService dictionaryService; private SearchService searchService; @@ -97,20 +99,23 @@ public class RepositoryAuthenticationDao implements MutableAuthenticationDao this.searchService = searchService; } - public UserDetails loadUserByUsername(String caseSensitiveUserName) throws UsernameNotFoundException, + public UserDetails loadUserByUsername(String incomingUserName) throws UsernameNotFoundException, DataAccessException { - String userName = userNamesAreCaseSensitive ? caseSensitiveUserName : caseSensitiveUserName.toLowerCase(); - NodeRef userRef = getUserOrNull(userName); + NodeRef userRef = getUserOrNull(incomingUserName); if (userRef == null) { - throw new UsernameNotFoundException("Could not find user by userName: " + caseSensitiveUserName); + throw new UsernameNotFoundException("Could not find user by userName: " + incomingUserName); } Map properties = nodeService.getProperties(userRef); String password = DefaultTypeConverter.INSTANCE.convert(String.class, properties .get(ContentModel.PROP_PASSWORD)); + // Report back the user name as stored on the user + String userName = DefaultTypeConverter.INSTANCE.convert(String.class, properties + .get(ContentModel.PROP_USER_USERNAME)); + GrantedAuthority[] gas = new GrantedAuthority[1]; gas[0] = new GrantedAuthorityImpl("ROLE_AUTHENTICATED"); @@ -119,12 +124,20 @@ public class RepositoryAuthenticationDao implements MutableAuthenticationDao return ud; } - public NodeRef getUserOrNull(String caseSensitiveUserName) + public NodeRef getUserOrNull(String searchUserName) { - String userName = userNamesAreCaseSensitive ? caseSensitiveUserName : caseSensitiveUserName.toLowerCase(); + if(searchUserName == null) + { + return null; + } + if(searchUserName.length() == 0) + { + return null; + } + SearchParameters sp = new SearchParameters(); sp.setLanguage(SearchService.LANGUAGE_LUCENE); - sp.setQuery("@usr\\:username:" + userName); + sp.setQuery("@usr\\:username:\"" + searchUserName + "\""); sp.addStore(STOREREF_USERS); sp.excludeDataInTheCurrentTransaction(false); @@ -134,6 +147,8 @@ public class RepositoryAuthenticationDao implements MutableAuthenticationDao { rs = searchService.query(sp); + NodeRef returnRef = null; + for (ResultSetRow row : rs) { @@ -142,12 +157,39 @@ public class RepositoryAuthenticationDao implements MutableAuthenticationDao { String realUserName = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty( nodeRef, ContentModel.PROP_USER_USERNAME)); - if (realUserName.equals(userName)) + + if (userNamesAreCaseSensitive) { - return nodeRef; + if (realUserName.equals(searchUserName)) + { + if(returnRef == null) + { + returnRef = nodeRef; + } + else + { + throw new AlfrescoRuntimeException("Found more than one user for "+searchUserName+ " (case sensitive)"); + } + } + } + else + { + if (realUserName.equalsIgnoreCase(searchUserName)) + { + if(returnRef == null) + { + returnRef = nodeRef; + } + else + { + throw new AlfrescoRuntimeException("Found more than one user for "+searchUserName+ " (case insensitive)"); + } + } } } } + + return returnRef; } finally { @@ -156,21 +198,18 @@ public class RepositoryAuthenticationDao implements MutableAuthenticationDao rs.close(); } } - - return null; } public void createUser(String caseSensitiveUserName, char[] rawPassword) throws AuthenticationException { - String userName = userNamesAreCaseSensitive ? caseSensitiveUserName : caseSensitiveUserName.toLowerCase(); - NodeRef userRef = getUserOrNull(userName); + NodeRef userRef = getUserOrNull(caseSensitiveUserName); if (userRef != null) { - throw new AuthenticationException("User already exists: " + userName); + throw new AuthenticationException("User already exists: " + caseSensitiveUserName); } NodeRef typesNode = getUserFolderLocation(); Map properties = new HashMap(); - properties.put(ContentModel.PROP_USER_USERNAME, userName); + properties.put(ContentModel.PROP_USER_USERNAME, caseSensitiveUserName); String salt = null; // GUID.generate(); properties.put(ContentModel.PROP_SALT, salt); properties.put(ContentModel.PROP_PASSWORD, passwordEncoder.encodePassword(new String(rawPassword), salt)); @@ -178,11 +217,7 @@ public class RepositoryAuthenticationDao implements MutableAuthenticationDao properties.put(ContentModel.PROP_CREDENTIALS_EXPIRE, Boolean.valueOf(false)); properties.put(ContentModel.PROP_ENABLED, Boolean.valueOf(true)); properties.put(ContentModel.PROP_ACCOUNT_LOCKED, Boolean.valueOf(false)); - nodeService.createNode( - typesNode, - ContentModel.ASSOC_CHILDREN, - ContentModel.TYPE_USER, - ContentModel.TYPE_USER, + nodeService.createNode(typesNode, ContentModel.ASSOC_CHILDREN, ContentModel.TYPE_USER, ContentModel.TYPE_USER, properties); } @@ -190,11 +225,10 @@ public class RepositoryAuthenticationDao implements MutableAuthenticationDao private NodeRef getUserFolderLocation() { QName qnameAssocSystem = QName.createQName("sys", "system", namespacePrefixResolver); - QName qnameAssocUsers = QName.createQName("sys", "people", namespacePrefixResolver); // see AR-527 + QName qnameAssocUsers = QName.createQName("sys", "people", namespacePrefixResolver); // see + // AR-527 NodeRef rootNode = nodeService.getRootNode(STOREREF_USERS); - List results = nodeService.getChildAssocs( - rootNode, - RegexQNamePattern.MATCH_ALL, + List results = nodeService.getChildAssocs(rootNode, RegexQNamePattern.MATCH_ALL, qnameAssocSystem); NodeRef sysNodeRef = null; if (results.size() == 0) @@ -205,10 +239,7 @@ public class RepositoryAuthenticationDao implements MutableAuthenticationDao { sysNodeRef = results.get(0).getChildRef(); } - results = nodeService.getChildAssocs( - sysNodeRef, - RegexQNamePattern.MATCH_ALL, - qnameAssocUsers); + results = nodeService.getChildAssocs(sysNodeRef, RegexQNamePattern.MATCH_ALL, qnameAssocUsers); NodeRef userNodeRef = null; if (results.size() == 0) { diff --git a/source/java/org/alfresco/repo/security/authentication/ldap/LDAPGroupExportSource.java b/source/java/org/alfresco/repo/security/authentication/ldap/LDAPGroupExportSource.java index a52fc2c438..2f4b366cac 100644 --- a/source/java/org/alfresco/repo/security/authentication/ldap/LDAPGroupExportSource.java +++ b/source/java/org/alfresco/repo/security/authentication/ldap/LDAPGroupExportSource.java @@ -19,7 +19,6 @@ package org.alfresco.repo.security.authentication.ldap; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; -import java.io.IOException; import java.io.Writer; import java.util.Collection; import java.util.HashMap; diff --git a/source/java/org/alfresco/repo/security/authentication/ntlm/NTLMAuthenticationComponentImpl.java b/source/java/org/alfresco/repo/security/authentication/ntlm/NTLMAuthenticationComponentImpl.java index a5eb84628b..7bdbbf5b5d 100644 --- a/source/java/org/alfresco/repo/security/authentication/ntlm/NTLMAuthenticationComponentImpl.java +++ b/source/java/org/alfresco/repo/security/authentication/ntlm/NTLMAuthenticationComponentImpl.java @@ -662,10 +662,9 @@ public class NTLMAuthenticationComponentImpl extends AbstractAuthenticationCompo } else { - // Set using the user name, lowercase the name if hte person service is case insensitive + // Set using the user name - if ( m_personService.getUserNamesAreCaseSensitive() == false) - username = username.toLowerCase(); + setCurrentUser( username); // DEBUG @@ -838,10 +837,8 @@ public class NTLMAuthenticationComponentImpl extends AbstractAuthenticationCompo } else { - // Set using the user name, lowercase the name if the person service is case insensitive + // Set using the user name - if ( m_personService.getUserNamesAreCaseSensitive() == false) - username = username.toLowerCase(); setCurrentUser( username); // DEBUG diff --git a/source/java/org/alfresco/repo/security/authentication/ntlm/NullMutableAuthenticationDao.java b/source/java/org/alfresco/repo/security/authentication/ntlm/NullMutableAuthenticationDao.java index 4472228415..4b6f3bbb01 100644 --- a/source/java/org/alfresco/repo/security/authentication/ntlm/NullMutableAuthenticationDao.java +++ b/source/java/org/alfresco/repo/security/authentication/ntlm/NullMutableAuthenticationDao.java @@ -294,18 +294,6 @@ public class NullMutableAuthenticationDao implements MutableAuthenticationDao // return null; } - - /** - * Are user names case sensitive? - * - * @return - */ - public boolean getUserNamesAreCaseSensitive() - { - throw new AlfrescoRuntimeException("Not implemented"); - -// return false; - } /** * Return the user details for the specified user diff --git a/source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java b/source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java index 0fa7e6ed31..2d4c4e618d 100644 --- a/source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java +++ b/source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java @@ -61,7 +61,7 @@ public class AuthorityDAOImpl implements AuthorityDAO private DictionaryService dictionaryService; - private SimpleCache> userToAuthorityCache; + private SimpleCache> userToAuthorityCache; public AuthorityDAOImpl() { @@ -90,7 +90,7 @@ public class AuthorityDAOImpl implements AuthorityDAO this.searchService = searchService; } - public void setUserToAuthorityCache(SimpleCache> userToAuthorityCache) + public void setUserToAuthorityCache(SimpleCache> userToAuthorityCache) { this.userToAuthorityCache = userToAuthorityCache; } @@ -118,7 +118,7 @@ public class AuthorityDAOImpl implements AuthorityDAO nodeService.setProperty(parentRef, ContentModel.PROP_MEMBERS, members); userToAuthorityCache.remove(childName); } - else + else if (AuthorityType.getAuthorityType(childName).equals(AuthorityType.GROUP)) { NodeRef childRef = getAuthorityOrNull(childName); if (childRef == null) @@ -127,6 +127,11 @@ public class AuthorityDAOImpl implements AuthorityDAO } nodeService.addChild(parentRef, childRef, ContentModel.ASSOC_MEMBER, QName.createQName("usr", childName, namespacePrefixResolver)); + userToAuthorityCache.clear(); + } + else + { + throw new AlfrescoRuntimeException("Authorities of the type "+AuthorityType.getAuthorityType(childName)+" may not be added to other authorities"); } } @@ -161,7 +166,7 @@ public class AuthorityDAOImpl implements AuthorityDAO throw new UnknownAuthorityException("An authority was not found for " + name); } nodeService.deleteNode(nodeRef); - + userToAuthorityCache.clear(); } public Set getAllRootAuthorities(AuthorityType type) @@ -230,15 +235,31 @@ public class AuthorityDAOImpl implements AuthorityDAO throw new UnknownAuthorityException("An authority was not found for " + childName); } nodeService.removeChild(parentRef, childRef); + userToAuthorityCache.clear(); } } public Set getContainingAuthorities(AuthorityType type, String name, boolean immediate) { - HashSet authorities = new HashSet(); - findAuthorities(type, name, authorities, true, !immediate); - return authorities; + if (AuthorityType.getAuthorityType(name).equals(AuthorityType.USER) && ! immediate && (type == null)) + { + // Cache user to authority look ups + HashSet authorities = userToAuthorityCache.get(name); + if(authorities == null) + { + authorities = new HashSet(); + findAuthorities(type, name, authorities, true, !immediate); + userToAuthorityCache.put(name, authorities); + } + return authorities; + } + else + { + HashSet authorities = new HashSet(); + findAuthorities(type, name, authorities, true, !immediate); + return authorities; + } } private void findAuthorities(AuthorityType type, String name, Set authorities, boolean parents, @@ -272,12 +293,7 @@ public class AuthorityDAOImpl implements AuthorityDAO private ArrayList getUserContainers(String name) { - ArrayList containers = userToAuthorityCache.get(name); - if (containers == null) - { - containers = findUserContainers(name); - userToAuthorityCache.put(name, containers); - } + ArrayList containers = findUserContainers(name); return containers; } diff --git a/source/java/org/alfresco/repo/security/authority/AuthorityServiceImpl.java b/source/java/org/alfresco/repo/security/authority/AuthorityServiceImpl.java index ca8a0d1cd1..f1103dc8e7 100644 --- a/source/java/org/alfresco/repo/security/authority/AuthorityServiceImpl.java +++ b/source/java/org/alfresco/repo/security/authority/AuthorityServiceImpl.java @@ -156,6 +156,13 @@ public class AuthorityServiceImpl implements AuthorityService public void addAuthority(String parentName, String childName) { + if (AuthorityType.getAuthorityType(childName).equals(AuthorityType.USER)) + { + if(!personService.personExists(childName)) + { + throw new AuthorityException("The person "+childName+" does not exist and can not be added to a group"); + } + } authorityDAO.addAuthority(parentName, childName); } diff --git a/source/java/org/alfresco/repo/security/authority/AuthorityServiceTest.java b/source/java/org/alfresco/repo/security/authority/AuthorityServiceTest.java index 4bf6925881..5c28b1e4de 100644 --- a/source/java/org/alfresco/repo/security/authority/AuthorityServiceTest.java +++ b/source/java/org/alfresco/repo/security/authority/AuthorityServiceTest.java @@ -310,6 +310,8 @@ public class AuthorityServiceTest extends TestCase public void testCreateAuthTree() { + personService.getPerson("andy"); + String auth1; String auth2; String auth3; @@ -339,12 +341,12 @@ public class AuthorityServiceTest extends TestCase assertEquals(5, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); - assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.USER).size()); + assertEquals(3, pubAuthorityService.getAllAuthorities(AuthorityType.USER).size()); pubAuthorityService.addAuthority(auth5, "andy"); assertEquals(5, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); // The next call looks for people not users :-) - assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.USER).size()); + assertEquals(3, pubAuthorityService.getAllAuthorities(AuthorityType.USER).size()); assertEquals(2, pubAuthorityService.getContainingAuthorities(null, "andy", false).size()); assertTrue(pubAuthorityService.getContainingAuthorities(null, "andy", false).contains(auth5)); assertTrue(pubAuthorityService.getContainingAuthorities(null, "andy", false).contains(auth2)); @@ -362,7 +364,7 @@ public class AuthorityServiceTest extends TestCase assertEquals(5, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); // The next call looks for people not users :-) - assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.USER).size()); + assertEquals(3, pubAuthorityService.getAllAuthorities(AuthorityType.USER).size()); assertEquals(0, pubAuthorityService.getContainingAuthorities(null, "andy", false).size()); assertEquals(1, pubAuthorityService.getContainingAuthorities(null, auth5, false).size()); assertTrue(pubAuthorityService.getContainingAuthorities(null, auth5, false).contains(auth2)); @@ -375,6 +377,8 @@ public class AuthorityServiceTest extends TestCase public void testCreateAuthNet() { + personService.getPerson("andy"); + String auth1; String auth2; String auth3; @@ -399,14 +403,14 @@ public class AuthorityServiceTest extends TestCase assertEquals(5, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); - assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.USER).size()); + assertEquals(3, pubAuthorityService.getAllAuthorities(AuthorityType.USER).size()); pubAuthorityService.addAuthority(auth5, "andy"); pubAuthorityService.addAuthority(auth1, "andy"); assertEquals(5, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); // The next call looks for people not users :-) - assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.USER).size()); + assertEquals(3, pubAuthorityService.getAllAuthorities(AuthorityType.USER).size()); assertEquals(3, pubAuthorityService.getContainingAuthorities(null, "andy", false).size()); assertTrue(pubAuthorityService.getContainingAuthorities(null, "andy", false).contains(auth5)); assertTrue(pubAuthorityService.getContainingAuthorities(null, "andy", false).contains(auth2)); @@ -425,7 +429,7 @@ public class AuthorityServiceTest extends TestCase assertEquals(5, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); // The next call looks for people not users :-) - assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.USER).size()); + assertEquals(3, pubAuthorityService.getAllAuthorities(AuthorityType.USER).size()); assertEquals(2, pubAuthorityService.getContainingAuthorities(null, "andy", false).size()); assertTrue(pubAuthorityService.getContainingAuthorities(null, "andy", false).contains(auth5)); assertTrue(pubAuthorityService.getContainingAuthorities(null, "andy", false).contains(auth2)); @@ -440,6 +444,8 @@ public class AuthorityServiceTest extends TestCase public void testCreateAuthNet2() { + personService.getPerson("andy"); + String auth1; String auth2; String auth3; @@ -464,14 +470,14 @@ public class AuthorityServiceTest extends TestCase assertEquals(5, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); - assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.USER).size()); + assertEquals(3, pubAuthorityService.getAllAuthorities(AuthorityType.USER).size()); pubAuthorityService.addAuthority(auth5, "andy"); pubAuthorityService.addAuthority(auth1, "andy"); assertEquals(5, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); // The next call looks for people not users :-) - assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.USER).size()); + assertEquals(3, pubAuthorityService.getAllAuthorities(AuthorityType.USER).size()); assertEquals(3, pubAuthorityService.getContainingAuthorities(null, "andy", false).size()); assertTrue(pubAuthorityService.getContainingAuthorities(null, "andy", false).contains(auth5)); assertTrue(pubAuthorityService.getContainingAuthorities(null, "andy", false).contains(auth2)); @@ -491,7 +497,7 @@ public class AuthorityServiceTest extends TestCase assertEquals(5, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); // The next call looks for people not users :-) - assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.USER).size()); + assertEquals(3, pubAuthorityService.getAllAuthorities(AuthorityType.USER).size()); assertEquals(4, pubAuthorityService.getContainingAuthorities(null, "andy", false).size()); assertTrue(pubAuthorityService.getContainingAuthorities(null, "andy", false).contains(auth5)); assertTrue(pubAuthorityService.getContainingAuthorities(null, "andy", false).contains(auth2)); diff --git a/source/java/org/alfresco/repo/security/authority/ExtendedPermissionServiceTest.java b/source/java/org/alfresco/repo/security/authority/ExtendedPermissionServiceTest.java index cdf1fde628..ba0d590e50 100644 --- a/source/java/org/alfresco/repo/security/authority/ExtendedPermissionServiceTest.java +++ b/source/java/org/alfresco/repo/security/authority/ExtendedPermissionServiceTest.java @@ -26,6 +26,8 @@ public class ExtendedPermissionServiceTest extends AbstractPermissionTest { public void testGroupPermission() { + personService.getPerson("andy"); + runAs("andy"); assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ), @@ -41,6 +43,8 @@ public class ExtendedPermissionServiceTest extends AbstractPermissionTest public void testDeletePermissionByRecipient() { + personService.getPerson("andy"); + runAs("andy"); assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ), diff --git a/source/java/org/alfresco/repo/security/permissions/dynamic/LockOwnerDynamicAuthority.java b/source/java/org/alfresco/repo/security/permissions/dynamic/LockOwnerDynamicAuthority.java index 40cd3b8d36..f952d5dd2b 100644 --- a/source/java/org/alfresco/repo/security/permissions/dynamic/LockOwnerDynamicAuthority.java +++ b/source/java/org/alfresco/repo/security/permissions/dynamic/LockOwnerDynamicAuthority.java @@ -16,10 +16,13 @@ */ package org.alfresco.repo.security.permissions.dynamic; +import org.alfresco.model.ContentModel; import org.alfresco.repo.security.permissions.DynamicAuthority; import org.alfresco.service.cmr.lock.LockService; import org.alfresco.service.cmr.lock.LockStatus; import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; import org.alfresco.service.cmr.security.PermissionService; import org.springframework.beans.factory.InitializingBean; @@ -28,6 +31,9 @@ public class LockOwnerDynamicAuthority implements DynamicAuthority, Initializing { private LockService lockService; + + private NodeService nodeService; + public LockOwnerDynamicAuthority() { @@ -36,7 +42,19 @@ public class LockOwnerDynamicAuthority implements DynamicAuthority, Initializing public boolean hasAuthority(NodeRef nodeRef, String userName) { - return lockService.getLockStatus(nodeRef) == LockStatus.LOCK_OWNER; + if(lockService.getLockStatus(nodeRef) == LockStatus.LOCK_OWNER) + { + return true; + } + if(nodeService.hasAspect(nodeRef, ContentModel.ASPECT_WORKING_COPY)) + { + NodeRef originial = DefaultTypeConverter.INSTANCE.convert(NodeRef.class, nodeService.getProperty(nodeRef, ContentModel.PROP_COPY_REFERENCE)); + return (lockService.getLockStatus(originial) == LockStatus.LOCK_OWNER); + } + else + { + return false; + } } public String getAuthority() @@ -48,7 +66,11 @@ public class LockOwnerDynamicAuthority implements DynamicAuthority, Initializing { if(lockService == null) { - throw new IllegalStateException("A lock service must be set"); + throw new IllegalStateException("The LockService must be set"); + } + if(nodeService == null) + { + throw new IllegalStateException("The NodeService service must be set"); } } @@ -58,6 +80,12 @@ public class LockOwnerDynamicAuthority implements DynamicAuthority, Initializing this.lockService = lockService; } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + } diff --git a/source/java/org/alfresco/repo/security/permissions/dynamic/LockOwnerDynamicAuthorityTest.java b/source/java/org/alfresco/repo/security/permissions/dynamic/LockOwnerDynamicAuthorityTest.java index 6504f772a7..2c383bf7f4 100644 --- a/source/java/org/alfresco/repo/security/permissions/dynamic/LockOwnerDynamicAuthorityTest.java +++ b/source/java/org/alfresco/repo/security/permissions/dynamic/LockOwnerDynamicAuthorityTest.java @@ -24,6 +24,7 @@ import org.alfresco.model.ContentModel; import org.alfresco.repo.security.authentication.AuthenticationComponent; import org.alfresco.repo.security.authentication.MutableAuthenticationDao; import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.coci.CheckOutCheckInService; import org.alfresco.service.cmr.lock.LockService; import org.alfresco.service.cmr.lock.LockStatus; import org.alfresco.service.cmr.lock.LockType; @@ -32,6 +33,7 @@ import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.security.AccessStatus; import org.alfresco.service.cmr.security.AuthenticationService; +import org.alfresco.service.cmr.security.OwnableService; import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.ApplicationContextHelper; @@ -50,7 +52,7 @@ public class LockOwnerDynamicAuthorityTest extends TestCase private MutableAuthenticationDao authenticationDAO; private LockService lockService; - + private NodeRef rootNodeRef; private UserTransaction userTransaction; @@ -59,6 +61,10 @@ public class LockOwnerDynamicAuthorityTest extends TestCase private LockOwnerDynamicAuthority dynamicAuthority; + private CheckOutCheckInService checkOutCheckInService; + + private OwnableService ownableService; + public LockOwnerDynamicAuthorityTest() { super(); @@ -78,6 +84,9 @@ public class LockOwnerDynamicAuthorityTest extends TestCase permissionService = (PermissionService) ctx.getBean("permissionService"); authenticationDAO = (MutableAuthenticationDao) ctx.getBean("alfDaoImpl"); + checkOutCheckInService = (CheckOutCheckInService) ctx.getBean("checkOutCheckInService"); + ownableService = (OwnableService) ctx.getBean("ownableService"); + authenticationComponent.setCurrentUser(authenticationComponent.getSystemUserName()); TransactionService transactionService = (TransactionService) ctx.getBean(ServiceRegistry.TRANSACTION_SERVICE @@ -150,7 +159,6 @@ public class LockOwnerDynamicAuthorityTest extends TestCase assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(rootNodeRef, PermissionService.CHECK_OUT)); assertEquals(AccessStatus.DENIED, permissionService.hasPermission(rootNodeRef, PermissionService.CHECK_IN)); assertEquals(AccessStatus.DENIED, permissionService.hasPermission(rootNodeRef, PermissionService.CANCEL_CHECK_OUT)); - } public void testPermissionWithLockAspect() @@ -212,5 +220,224 @@ public class LockOwnerDynamicAuthorityTest extends TestCase } + public void testCheckOutCheckInAuthorities() + { + permissionService.setPermission(rootNodeRef, "andy", PermissionService.ALL_PERMISSIONS, true); + permissionService.setPermission(rootNodeRef, "lemur", PermissionService.CHECK_OUT, true); + permissionService.setPermission(rootNodeRef, "lemur", PermissionService.WRITE, true); + permissionService.setPermission(rootNodeRef, "lemur", PermissionService.READ, true); + permissionService.setPermission(rootNodeRef, "frog", PermissionService.CHECK_OUT, true); + permissionService.setPermission(rootNodeRef, "frog", PermissionService.WRITE, true); + permissionService.setPermission(rootNodeRef, "frog", PermissionService.READ, true); + + authenticationService.authenticate("andy", "andy".toCharArray()); + NodeRef testNode = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, ContentModel.TYPE_PERSON, + ContentModel.TYPE_CMOBJECT, null).getChildRef(); + permissionService.setPermission(rootNodeRef, "andy", PermissionService.ALL_PERMISSIONS, false); + + + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, + PermissionService.LOCK)); + assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, + PermissionService.UNLOCK)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.CHECK_OUT)); + assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, PermissionService.CHECK_IN)); + assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, PermissionService.CANCEL_CHECK_OUT)); + + authenticationService.authenticate("lemur", "lemur".toCharArray()); + + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, + PermissionService.LOCK)); + assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, + PermissionService.UNLOCK)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.CHECK_OUT)); + assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, PermissionService.CHECK_IN)); + assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, PermissionService.CANCEL_CHECK_OUT)); + + authenticationService.authenticate("frog", "frog".toCharArray()); + + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, + PermissionService.LOCK)); + assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, + PermissionService.UNLOCK)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.CHECK_OUT)); + assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, PermissionService.CHECK_IN)); + assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, PermissionService.CANCEL_CHECK_OUT)); + + // Check out as frog + NodeRef workingCopy = checkOutCheckInService.checkout(testNode); + + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, + PermissionService.LOCK)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, + PermissionService.UNLOCK)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.CHECK_OUT)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.CHECK_IN)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.CANCEL_CHECK_OUT)); + + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(workingCopy, + PermissionService.LOCK)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(workingCopy, + PermissionService.UNLOCK)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(workingCopy, PermissionService.CHECK_OUT)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(workingCopy, PermissionService.CHECK_IN)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(workingCopy, PermissionService.CANCEL_CHECK_OUT)); + + authenticationService.authenticate("lemur", "lemur".toCharArray()); + + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, + PermissionService.LOCK)); + assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, + PermissionService.UNLOCK)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.CHECK_OUT)); + assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, PermissionService.CHECK_IN)); + assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, PermissionService.CANCEL_CHECK_OUT)); + + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(workingCopy, + PermissionService.LOCK)); + assertEquals(AccessStatus.DENIED, permissionService.hasPermission(workingCopy, + PermissionService.UNLOCK)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(workingCopy, PermissionService.CHECK_OUT)); + assertEquals(AccessStatus.DENIED, permissionService.hasPermission(workingCopy, PermissionService.CHECK_IN)); + assertEquals(AccessStatus.DENIED, permissionService.hasPermission(workingCopy, PermissionService.CANCEL_CHECK_OUT)); + + + // set owner ...frog only has permissions of dynamic lock owner in wc and sourec + authenticationService.authenticate("frog", "frog".toCharArray()); + ownableService.setOwner(workingCopy, "lemur"); + + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, + PermissionService.LOCK)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, + PermissionService.UNLOCK)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.CHECK_OUT)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.CHECK_IN)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.CANCEL_CHECK_OUT)); + + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(workingCopy, + PermissionService.LOCK)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(workingCopy, + PermissionService.UNLOCK)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(workingCopy, PermissionService.CHECK_OUT)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(workingCopy, PermissionService.CHECK_IN)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(workingCopy, PermissionService.CANCEL_CHECK_OUT)); + + // test the new owner.. + authenticationService.authenticate("lemur", "lemur".toCharArray()); + + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, + PermissionService.LOCK)); + assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, + PermissionService.UNLOCK)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.CHECK_OUT)); + assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, PermissionService.CHECK_IN)); + assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, PermissionService.CANCEL_CHECK_OUT)); + + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(workingCopy, + PermissionService.LOCK)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(workingCopy, + PermissionService.UNLOCK)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(workingCopy, PermissionService.CHECK_OUT)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(workingCopy, PermissionService.CHECK_IN)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(workingCopy, PermissionService.CANCEL_CHECK_OUT)); + + authenticationService.authenticate("frog", "frog".toCharArray()); + checkOutCheckInService.cancelCheckout(workingCopy); + + authenticationService.authenticate("andy", "andy".toCharArray()); + + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, + PermissionService.LOCK)); + assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, + PermissionService.UNLOCK)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.CHECK_OUT)); + assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, PermissionService.CHECK_IN)); + assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, PermissionService.CANCEL_CHECK_OUT)); + + authenticationService.authenticate("lemur", "lemur".toCharArray()); + + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, + PermissionService.LOCK)); + assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, + PermissionService.UNLOCK)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.CHECK_OUT)); + assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, PermissionService.CHECK_IN)); + assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, PermissionService.CANCEL_CHECK_OUT)); + + authenticationService.authenticate("frog", "frog".toCharArray()); + + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, + PermissionService.LOCK)); + assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, + PermissionService.UNLOCK)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.CHECK_OUT)); + assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, PermissionService.CHECK_IN)); + assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, PermissionService.CANCEL_CHECK_OUT)); + + + authenticationService.authenticate("frog", "frog".toCharArray()); + workingCopy = checkOutCheckInService.checkout(testNode); + ownableService.setOwner(workingCopy, "lemur"); + checkOutCheckInService.checkin(workingCopy, null); + + } + public void testCeckInCheckOut() + { + + permissionService.setPermission(rootNodeRef, "andy", PermissionService.ALL_PERMISSIONS, true); + permissionService.setPermission(rootNodeRef, "lemur", PermissionService.CHECK_OUT, true); + permissionService.setPermission(rootNodeRef, "lemur", PermissionService.WRITE, true); + permissionService.setPermission(rootNodeRef, "lemur", PermissionService.READ, true); + permissionService.setPermission(rootNodeRef, "frog", PermissionService.CHECK_OUT, true); + permissionService.setPermission(rootNodeRef, "frog", PermissionService.WRITE, true); + permissionService.setPermission(rootNodeRef, "frog", PermissionService.READ, true); + authenticationService.authenticate("andy", "andy".toCharArray()); + NodeRef testNode = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, ContentModel.TYPE_PERSON, + ContentModel.TYPE_CMOBJECT, null).getChildRef(); + lockService.lock(testNode, LockType.READ_ONLY_LOCK); + + + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, + PermissionService.LOCK)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, + PermissionService.UNLOCK)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.CHECK_OUT)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.CHECK_IN)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.CANCEL_CHECK_OUT)); + + authenticationService.authenticate("lemur", "lemur".toCharArray()); + + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, + PermissionService.LOCK)); + assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, + PermissionService.UNLOCK)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.CHECK_OUT)); + assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, PermissionService.CHECK_IN)); + assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, PermissionService.CANCEL_CHECK_OUT)); + + authenticationService.authenticate("andy", "andy".toCharArray()); + lockService.unlock(testNode); + authenticationService.authenticate("lemur", "lemur".toCharArray()); + lockService.lock(testNode, LockType.READ_ONLY_LOCK); + + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, + PermissionService.LOCK)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, + PermissionService.UNLOCK)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.CHECK_OUT)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.CHECK_IN)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.CANCEL_CHECK_OUT)); + + + authenticationService.authenticate("frog", "frog".toCharArray()); + + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, + PermissionService.LOCK)); + assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, + PermissionService.UNLOCK)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.CHECK_OUT)); + assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, PermissionService.CHECK_IN)); + assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, PermissionService.CANCEL_CHECK_OUT)); + } } diff --git a/source/java/org/alfresco/repo/security/permissions/impl/ExceptionTranslatorMethodInterceptor.java b/source/java/org/alfresco/repo/security/permissions/impl/ExceptionTranslatorMethodInterceptor.java index ff6e421707..fc9a769f05 100644 --- a/source/java/org/alfresco/repo/security/permissions/impl/ExceptionTranslatorMethodInterceptor.java +++ b/source/java/org/alfresco/repo/security/permissions/impl/ExceptionTranslatorMethodInterceptor.java @@ -27,6 +27,7 @@ import org.springframework.dao.InvalidDataAccessApiUsageException; public class ExceptionTranslatorMethodInterceptor implements MethodInterceptor { private static final String MSG_ACCESS_DENIED = "permissions.err_access_denied"; + private static final String MSG_READ_ONLY = "permissions.err_read_only"; public ExceptionTranslatorMethodInterceptor() { @@ -46,7 +47,7 @@ public class ExceptionTranslatorMethodInterceptor implements MethodInterceptor catch (InvalidDataAccessApiUsageException e) { // this usually occurs when the server is in read-only mode - throw new AccessDeniedException(MSG_ACCESS_DENIED, e); + throw new AccessDeniedException(MSG_READ_ONLY, e); } } } 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 cb246f5cdf..db1725cab5 100644 --- a/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceImpl.java +++ b/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceImpl.java @@ -18,6 +18,7 @@ package org.alfresco.repo.security.permissions.impl; import java.io.Serializable; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Set; @@ -25,7 +26,10 @@ import net.sf.acegisecurity.Authentication; import net.sf.acegisecurity.GrantedAuthority; import net.sf.acegisecurity.providers.dao.User; +import org.alfresco.model.ContentModel; import org.alfresco.repo.cache.SimpleCache; +import org.alfresco.repo.policy.JavaBehaviour; +import org.alfresco.repo.policy.PolicyComponent; import org.alfresco.repo.security.authentication.AuthenticationComponent; import org.alfresco.repo.security.permissions.DynamicAuthority; import org.alfresco.repo.security.permissions.NodePermissionEntry; @@ -41,6 +45,7 @@ import org.alfresco.service.cmr.security.AccessStatus; import org.alfresco.service.cmr.security.AuthorityService; import org.alfresco.service.cmr.security.AuthorityType; import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.util.EqualsHelper; import org.apache.commons.logging.Log; @@ -100,6 +105,8 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing */ private List dynamicAuthorities; + private PolicyComponent policyComponent; + /* * Standard spring construction. */ @@ -150,13 +157,24 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing /** * Set the permissions access cache. * - * @param accessCache a transactionally safe cache + * @param accessCache + * a transactionally safe cache */ public void setAccessCache(SimpleCache accessCache) { this.accessCache = accessCache; } + public void setPolicyComponent(PolicyComponent policyComponent) + { + this.policyComponent = policyComponent; + } + + public void onMoveNode(ChildAssociationRef oldChildAssocRef, ChildAssociationRef newChildAssocRef) + { + accessCache.clear(); + } + public void afterPropertiesSet() throws Exception { if (dictionaryService == null) @@ -187,6 +205,13 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing { throw new IllegalArgumentException("Property 'accessCache' has not been set"); } + if (policyComponent == null) + { + throw new IllegalArgumentException("Property 'policyComponent' has not been set"); + } + + policyComponent.bindClassBehaviour(QName.createQName(NamespaceService.ALFRESCO_URI, "onMoveNode"), ContentModel.ASPECT_AUDITABLE, new JavaBehaviour(this, "onMoveNode")); + } // @@ -329,7 +354,7 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing public Set getSettablePermissions(QName type) { Set settable = getSettablePermissionReferences(type); - Set strings = new HashSet(settable.size()); + Set strings = new LinkedHashSet(settable.size()); for (PermissionReference pr : settable) { strings.add(getPermission(pr)); @@ -421,15 +446,17 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing } /** - * Key for a cache object is built from all the known Authorities (which can change - * dynamically so they must all be used) the NodeRef ID and the permission reference itself. - * This gives a unique key for each permission test. + * Key for a cache object is built from all the known Authorities (which can + * change dynamically so they must all be used) the NodeRef ID and the + * permission reference itself. This gives a unique key for each permission + * test. */ - static Serializable generateKey(Set auths, NodeRef ref, PermissionReference perm) + static Serializable generateKey(Set auths, NodeRef nodeRef, PermissionReference perm) { - HashSet key = new HashSet(auths); - key.add(ref.getId()); + LinkedHashSet key = new LinkedHashSet(); key.add(perm.toString()); + key.addAll(auths); + key.add(nodeRef); return key; } 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 4f21df35ad..4a620b9173 100644 --- a/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceTest.java +++ b/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceTest.java @@ -59,10 +59,7 @@ public class PermissionServiceTest extends AbstractPermissionTest Authentication auth = authenticationComponent.getCurrentAuthentication(); for (GrantedAuthority authority : auth.getAuthorities()) { - if (authority.getAuthority().equals(ROLE_AUTHENTICATED)) - { - return; - } + if (authority.getAuthority().equals(ROLE_AUTHENTICATED)) { return; } } fail("Missing role ROLE_AUTHENTICATED "); } @@ -142,6 +139,32 @@ public class PermissionServiceTest extends AbstractPermissionTest } + public void testPermissionCacheOnMove() + { + runAs("admin"); + + 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(); + + permissionService.setPermission(new SimplePermissionEntry(n1, getPermission(PermissionService.READ), "andy", + AccessStatus.ALLOWED)); + + runAs("andy"); + + assertTrue(permissionService.hasPermission(n1, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + + runAs("admin"); + nodeService.moveNode(n2, rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}oneMoved")); + + runAs("andy"); + + assertTrue(permissionService.hasPermission(n1, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.DENIED); + } + public void testSetInheritFalse() { runAs("andy"); @@ -222,7 +245,7 @@ public class PermissionServiceTest extends AbstractPermissionTest testSetNodePermissionEntry(); testSetNodePermissionEntry2(); } - + public void testDoubleSetAllowDeny() { Set permissionEntries = null; @@ -618,6 +641,9 @@ public class PermissionServiceTest extends AbstractPermissionTest assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); // Changed ny not enfocing READ // assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + // assertFalse(permissionService.hasPermission(n1, + // getPermission(PermissionService.READ_PROPERTIES)) == + // AccessStatus.ALLOWED); assertTrue(permissionService.hasPermission(n1, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); runAs("lemur"); assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); @@ -1479,6 +1505,59 @@ public class PermissionServiceTest extends AbstractPermissionTest assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); } + public void testPermissionCase() + { + + 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_CHILDREN), "Andy", AccessStatus.ALLOWED)); + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, + getPermission(PermissionService.READ_PROPERTIES), "ANDY", AccessStatus.ALLOWED)); + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, + getPermission(PermissionService.READ_CONTENT), "AnDy", AccessStatus.ALLOWED)); + + 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_CHILDREN), "andy", AccessStatus.ALLOWED)); +// permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, +// getPermission(PermissionService.READ_PROPERTIES), "andy", AccessStatus.ALLOWED)); +// permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, +// getPermission(PermissionService.READ_CONTENT), "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); + + } + public void testEffectiveComposite() { diff --git a/source/java/org/alfresco/repo/security/permissions/impl/model/PermissionModel.java b/source/java/org/alfresco/repo/security/permissions/impl/model/PermissionModel.java index 3fab3584d6..e374595c68 100644 --- a/source/java/org/alfresco/repo/security/permissions/impl/model/PermissionModel.java +++ b/source/java/org/alfresco/repo/security/permissions/impl/model/PermissionModel.java @@ -22,6 +22,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; @@ -96,13 +97,16 @@ public class PermissionModel implements ModelDAO, InitializingBean private AccessStatus defaultPermission; // Cache granting permissions - private HashMap> grantingPermissions = new HashMap>(); + private HashMap> grantingPermissions = + new HashMap>(); // Cache grantees - private HashMap> granteePermissions = new HashMap>(); + private HashMap> granteePermissions = + new HashMap>(); // Cache the mapping of extended groups to the base - private HashMap groupsToBaseGroup = new HashMap(); + private HashMap groupsToBaseGroup = + new HashMap(); private HashMap uniqueMap; @@ -111,7 +115,13 @@ public class PermissionModel implements ModelDAO, InitializingBean private HashMap permissionGroupMap; private HashMap permissionReferenceMap; - + + private Map> cachedTypePermissionsExposed = + new HashMap>(128, 1.0f); + + private Map> cachedTypePermissionsUnexposed = + new HashMap>(128, 1.0f); + public PermissionModel() { super(); @@ -206,7 +216,6 @@ public class PermissionModel implements ModelDAO, InitializingBean globalPermissions.add(globalPermission); } - } /* @@ -274,20 +283,35 @@ public class PermissionModel implements ModelDAO, InitializingBean { return getAllPermissionsImpl(type, true); } - + + @SuppressWarnings("unchecked") private Set getAllPermissionsImpl(QName type, boolean exposedOnly) { - Set permissions = new HashSet(); - if (dictionaryService.getClass(type).isAspect()) + Map> cache; + if (exposedOnly) { - addAspectPermissions(type, permissions, exposedOnly); + cache = this.cachedTypePermissionsExposed; } else { - mergeGeneralAspectPermissions(permissions, exposedOnly); - addTypePermissions(type, permissions, exposedOnly); + cache = this.cachedTypePermissionsUnexposed; } - return permissions; + LinkedHashSet permissions = cache.get(type); + if (permissions == null) + { + permissions = new LinkedHashSet(); + if (dictionaryService.getClass(type).isAspect()) + { + addAspectPermissions(type, permissions, exposedOnly); + } + else + { + mergeGeneralAspectPermissions(permissions, exposedOnly); + addTypePermissions(type, permissions, exposedOnly); + } + cache.put(type, permissions); + } + return (Set)permissions.clone(); } /** @@ -378,7 +402,6 @@ public class PermissionModel implements ModelDAO, InitializingBean } } - private void mergeGeneralAspectPermissions(Set target, boolean exposedOnly) { for(QName aspect : dictionaryService.getAllAspects()) @@ -399,11 +422,15 @@ public class PermissionModel implements ModelDAO, InitializingBean public Set getExposedPermissionsImpl(NodeRef nodeRef, boolean exposedOnly) { - + // + // TODO: cache permissions based on type and exposed flag + // create JMeter test to see before/after effect! + // QName typeName = nodeService.getType(nodeRef); + Set permissions = getAllPermissions(typeName); mergeGeneralAspectPermissions(permissions, exposedOnly); - // Add non mandatory aspects.. + // Add non mandatory aspects... Set defaultAspects = new HashSet(); for (AspectDefinition aspDef : dictionaryService.getType(typeName).getDefaultAspects()) { @@ -417,7 +444,6 @@ public class PermissionModel implements ModelDAO, InitializingBean } } return permissions; - } public synchronized Set getGrantingPermissions(PermissionReference permissionReference) diff --git a/source/java/org/alfresco/repo/security/permissions/impl/model/PermissionSet.java b/source/java/org/alfresco/repo/security/permissions/impl/model/PermissionSet.java index c6054e19a0..69af17d35c 100644 --- a/source/java/org/alfresco/repo/security/permissions/impl/model/PermissionSet.java +++ b/source/java/org/alfresco/repo/security/permissions/impl/model/PermissionSet.java @@ -19,6 +19,7 @@ package org.alfresco.repo.security.permissions.impl.model; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.Set; import org.alfresco.service.namespace.NamespacePrefixResolver; @@ -30,7 +31,7 @@ import org.dom4j.Element; * Store and read the definition of a permission set * @author andyh */ -public class PermissionSet implements XMLModelInitialisable +public final class PermissionSet implements XMLModelInitialisable { private static final String TYPE = "type"; private static final String PERMISSION_GROUP = "permissionGroup"; @@ -44,7 +45,7 @@ public class PermissionSet implements XMLModelInitialisable private boolean exposeAll; - private Set permissionGroups = new HashSet(); + private Set permissionGroups = new LinkedHashSet(); private Set permissions = new HashSet(); @@ -104,7 +105,4 @@ public class PermissionSet implements XMLModelInitialisable { return exposeAll; } - - - } diff --git a/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java b/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java index 7acae79d16..690dfa13c9 100644 --- a/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java +++ b/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java @@ -55,23 +55,23 @@ public class PersonServiceImpl implements PersonService private NodeService nodeService; private SearchService searchService; - + private AuthorityService authorityService; private PermissionServiceSPI permissionServiceSPI; - + private NamespacePrefixResolver namespacePrefixResolver; private boolean createMissingPeople; - private boolean userNamesAreCaseSensitive; - private String companyHomePath; private NodeRef companyHomeNodeRef; private static Set mutableProperties; + private boolean userNamesAreCaseSensitive = false; + static { Set props = new HashSet(); @@ -99,9 +99,8 @@ public class PersonServiceImpl implements PersonService this.userNamesAreCaseSensitive = userNamesAreCaseSensitive; } - public NodeRef getPerson(String caseSensitiveUserName) + public NodeRef getPerson(String userName) { - String userName = userNamesAreCaseSensitive ? caseSensitiveUserName : caseSensitiveUserName.toLowerCase(); NodeRef personNode = getPersonOrNull(userName); if (personNode == null) { @@ -126,12 +125,12 @@ public class PersonServiceImpl implements PersonService return getPersonOrNull(caseSensitiveUserName) != null; } - public NodeRef getPersonOrNull(String caseSensitiveUserName) + public NodeRef getPersonOrNull(String searchUserName) { - String userName = userNamesAreCaseSensitive ? caseSensitiveUserName : caseSensitiveUserName.toLowerCase(); SearchParameters sp = new SearchParameters(); sp.setLanguage(SearchService.LANGUAGE_LUCENE); - sp.setQuery("TYPE:\\{http\\://www.alfresco.org/model/content/1.0\\}person +@cm\\:userName:\"" + userName + "\""); + sp.setQuery("TYPE:\\{http\\://www.alfresco.org/model/content/1.0\\}person +@cm\\:userName:\"" + searchUserName + + "\""); sp.addStore(storeRef); sp.excludeDataInTheCurrentTransaction(false); @@ -141,22 +140,51 @@ public class PersonServiceImpl implements PersonService { rs = searchService.query(sp); + NodeRef returnRef = null; + for (ResultSetRow row : rs) { NodeRef nodeRef = row.getNodeRef(); if (nodeService.exists(nodeRef)) { - String realUserName = DefaultTypeConverter.INSTANCE.convert( - String.class, - nodeService.getProperty(nodeRef, ContentModel.PROP_USERNAME)); - realUserName = userNamesAreCaseSensitive ? realUserName : realUserName.toLowerCase(); - if (realUserName.equals(userName)) + String realUserName = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty( + nodeRef, ContentModel.PROP_USERNAME)); + + if (userNamesAreCaseSensitive) { - return nodeRef; + if (realUserName.equals(searchUserName)) + { + if (returnRef == null) + { + returnRef = nodeRef; + } + else + { + throw new AlfrescoRuntimeException("Found more than one user for " + searchUserName + + " (case sensitive)"); + } + } + } + else + { + if (realUserName.equalsIgnoreCase(searchUserName)) + { + if (returnRef == null) + { + returnRef = nodeRef; + } + else + { + throw new AlfrescoRuntimeException("Found more than one user for " + searchUserName + + " (case insensitive)"); + } + } } } } + + return returnRef; } finally { @@ -165,8 +193,6 @@ public class PersonServiceImpl implements PersonService rs.close(); } } - - return null; } public boolean createMissingPeople() @@ -179,9 +205,8 @@ public class PersonServiceImpl implements PersonService return mutableProperties; } - public void setPersonProperties(String caseSensitiveUserName, Map properties) + public void setPersonProperties(String userName, Map properties) { - String userName = userNamesAreCaseSensitive ? caseSensitiveUserName : caseSensitiveUserName.toLowerCase(); NodeRef personNode = getPersonOrNull(userName); if (personNode == null) { @@ -195,8 +220,12 @@ public class PersonServiceImpl implements PersonService } } - - properties.put(ContentModel.PROP_USERNAME, userName); + else + { + String realUserName = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(personNode, + ContentModel.PROP_USERNAME)); + properties.put(ContentModel.PROP_USERNAME, realUserName); + } nodeService.setProperties(personNode, properties); } @@ -231,26 +260,17 @@ public class PersonServiceImpl implements PersonService public NodeRef createPerson(Map properties) { - String caseSensitiveUserName = DefaultTypeConverter.INSTANCE.convert(String.class, properties + String userName = DefaultTypeConverter.INSTANCE.convert(String.class, properties .get(ContentModel.PROP_USERNAME)); - String userName = userNamesAreCaseSensitive ? caseSensitiveUserName : caseSensitiveUserName.toLowerCase(); properties.put(ContentModel.PROP_USERNAME, userName); - return nodeService.createNode( - getPeopleContainer(), - ContentModel.ASSOC_CHILDREN, - ContentModel.TYPE_PERSON, - ContentModel.TYPE_PERSON, - properties).getChildRef(); + return nodeService.createNode(getPeopleContainer(), ContentModel.ASSOC_CHILDREN, ContentModel.TYPE_PERSON, + ContentModel.TYPE_PERSON, properties).getChildRef(); } public NodeRef getPeopleContainer() { NodeRef rootNodeRef = nodeService.getRootNode(storeRef); - List results = searchService.selectNodes( - rootNodeRef, - PEOPLE_FOLDER, - null, - namespacePrefixResolver, + List results = searchService.selectNodes(rootNodeRef, PEOPLE_FOLDER, null, namespacePrefixResolver, false); if (results.size() == 0) { @@ -265,25 +285,22 @@ public class PersonServiceImpl implements PersonService public void deletePerson(String userName) { NodeRef personNodeRef = getPersonOrNull(userName); - + // delete the person if (personNodeRef != null) { nodeService.deleteNode(personNodeRef); } - // translate username based on user name case sensitivity - String authorityName = userNamesAreCaseSensitive ? userName : userName.toLowerCase(); - // remove user from any containing authorities Set containerAuthorities = authorityService.getContainingAuthorities(null, userName, true); for (String containerAuthority : containerAuthorities) { - authorityService.removeAuthority(containerAuthority, authorityName); + authorityService.removeAuthority(containerAuthority, userName); } - + // remove any user permissions - permissionServiceSPI.deletePermissions(authorityName); + permissionServiceSPI.deletePermissions(userName); } public Set getAllPeople() @@ -301,7 +318,6 @@ public class PersonServiceImpl implements PersonService { rs = searchService.query(sp); - for (ResultSetRow row : rs) { @@ -341,7 +357,7 @@ public class PersonServiceImpl implements PersonService { this.permissionServiceSPI = permissionServiceSPI; } - + public void setNodeService(NodeService nodeService) { this.nodeService = nodeService; @@ -377,6 +393,18 @@ public class PersonServiceImpl implements PersonService return companyHomeNodeRef; } + public String getUserIdentifier(String caseSensitiveUserName) + { + NodeRef nodeRef = getPersonOrNull(caseSensitiveUserName); + if ((nodeRef != null) && nodeService.exists(nodeRef)) + { + String realUserName = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(nodeRef, + ContentModel.PROP_USERNAME)); + return realUserName; + } + return null; + } + // IOC Setters } diff --git a/source/java/org/alfresco/repo/security/person/PersonTest.java b/source/java/org/alfresco/repo/security/person/PersonTest.java index 74afad5525..c898d1cf28 100644 --- a/source/java/org/alfresco/repo/security/person/PersonTest.java +++ b/source/java/org/alfresco/repo/security/person/PersonTest.java @@ -28,6 +28,7 @@ import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.namespace.QName; import org.alfresco.util.BaseSpringTest; +import org.alfresco.util.EqualsHelper; public class PersonTest extends BaseSpringTest { @@ -51,8 +52,8 @@ public class PersonTest extends BaseSpringTest StoreRef storeRef = nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, "Test_" + System.currentTimeMillis()); rootNodeRef = nodeService.getRootNode(storeRef); - - for(NodeRef nodeRef: personService.getAllPeople()) + + for (NodeRef nodeRef : personService.getAllPeople()) { nodeService.deleteNode(nodeRef); } @@ -67,44 +68,40 @@ public class PersonTest extends BaseSpringTest public void xtestPerformance() { personService.setCreateMissingPeople(false); - - personService.createPerson(createDefaultProperties("derek", "Derek", "Hulley", "dh@dh", - "alfresco", rootNodeRef)); - - - + + personService + .createPerson(createDefaultProperties("derek", "Derek", "Hulley", "dh@dh", "alfresco", rootNodeRef)); + long create = 0; - long count = 0; - + long start; long end; - - for(int i = 0; i < 10000; i++) + + for (int i = 0; i < 10000; i++) { - String id = "TestUser-"+i; + String id = "TestUser-" + i; start = System.nanoTime(); - personService.createPerson(createDefaultProperties(id, id, id, id, - id, rootNodeRef)); + personService.createPerson(createDefaultProperties(id, id, id, id, id, rootNodeRef)); end = System.nanoTime(); create += (end - start); - - if((i > 0) && (i % 100 == 0)) + + if ((i > 0) && (i % 100 == 0)) { - System.out.println("Count = "+i); - System.out.println("Average create : "+(create/i/1000000.0f)); + System.out.println("Count = " + i); + System.out.println("Average create : " + (create / i / 1000000.0f)); start = System.nanoTime(); personService.personExists(id); end = System.nanoTime(); - System.out.println("Exists : "+((end-start)/1000000.0f)); - + System.out.println("Exists : " + ((end - start) / 1000000.0f)); + start = System.nanoTime(); int size = personService.getAllPeople().size(); end = System.nanoTime(); - System.out.println("Size ("+size+") : "+((end-start)/1000000.0f)); + System.out.println("Size (" + size + ") : " + ((end - start) / 1000000.0f)); } } } - + public void testCreateMissingPeople1() { personService.setCreateMissingPeople(false); @@ -122,9 +119,9 @@ public class PersonTest extends BaseSpringTest catch (PersonException pe) { - } + } } - + public void testCreateMissingPeople2() { personService.setCreateMissingPeople(false); @@ -137,6 +134,17 @@ public class PersonTest extends BaseSpringTest assertNotNull(nodeRef); testProperties(nodeRef, "andy", "andy", "", "", ""); + nodeRef = personService.getPerson("Andy"); + assertNotNull(nodeRef); + if (personService.getUserIdentifier("Andy").equals("Andy")) + { + testProperties(nodeRef, "Andy", "Andy", "", "", ""); + } + else + { + testProperties(nodeRef, "andy", "andy", "", "", ""); + } + personService.setCreateMissingPeople(false); try { @@ -149,8 +157,7 @@ public class PersonTest extends BaseSpringTest } } - - + public void testCreateMissingPeople() { personService.setCreateMissingPeople(false); @@ -173,7 +180,7 @@ public class PersonTest extends BaseSpringTest assertEquals(2, personService.getAllPeople().size()); assertTrue(personService.getAllPeople().contains(personService.getPerson("andy"))); assertTrue(personService.getAllPeople().contains(personService.getPerson("derek"))); - + } public void testMutableProperties() @@ -184,7 +191,7 @@ public class PersonTest extends BaseSpringTest assertTrue(personService.getMutableProperties().contains(ContentModel.PROP_LASTNAME)); assertTrue(personService.getMutableProperties().contains(ContentModel.PROP_EMAIL)); assertTrue(personService.getMutableProperties().contains(ContentModel.PROP_ORGID)); - + } public void testPersonCRUD1() @@ -200,27 +207,27 @@ public class PersonTest extends BaseSpringTest } } - + public void testPersonCRUD2() { personService.setCreateMissingPeople(false); - personService.createPerson(createDefaultProperties("derek", "Derek", "Hulley", "dh@dh", - "alfresco", rootNodeRef)); + personService + .createPerson(createDefaultProperties("derek", "Derek", "Hulley", "dh@dh", "alfresco", rootNodeRef)); testProperties(personService.getPerson("derek"), "derek", "Derek", "Hulley", "dh@dh", "alfresco"); - + personService.setPersonProperties("derek", createDefaultProperties("derek", "Derek_", "Hulley_", "dh@dh_", - "alfresco_", rootNodeRef)); - + "alfresco_", rootNodeRef)); + testProperties(personService.getPerson("derek"), "derek", "Derek_", "Hulley_", "dh@dh_", "alfresco_"); - + personService.setPersonProperties("derek", createDefaultProperties("derek", "Derek", "Hulley", "dh@dh", "alfresco", rootNodeRef)); - + testProperties(personService.getPerson("derek"), "derek", "Derek", "Hulley", "dh@dh", "alfresco"); - + assertEquals(1, personService.getAllPeople().size()); assertTrue(personService.getAllPeople().contains(personService.getPerson("derek"))); - + personService.deletePerson("derek"); assertEquals(0, personService.getAllPeople().size()); try @@ -233,35 +240,39 @@ public class PersonTest extends BaseSpringTest } } - + public void testPersonCRUD() { personService.setCreateMissingPeople(false); - personService.createPerson(createDefaultProperties("derek", "Derek", "Hulley", "dh@dh", + personService + .createPerson(createDefaultProperties("Derek", "Derek", "Hulley", "dh@dh", "alfresco", rootNodeRef)); + testProperties(personService.getPerson("Derek"), "Derek", "Derek", "Hulley", "dh@dh", "alfresco"); + + personService.setPersonProperties("Derek", createDefaultProperties("derek", "Derek_", "Hulley_", "dh@dh_", + "alfresco_", rootNodeRef)); + + testProperties(personService.getPerson("Derek"), "Derek", "Derek_", "Hulley_", "dh@dh_", "alfresco_"); + + personService.setPersonProperties("Derek", createDefaultProperties("derek", "Derek", "Hulley", "dh@dh", "alfresco", rootNodeRef)); - testProperties(personService.getPerson("derek"), "derek", "Derek", "Hulley", "dh@dh", "alfresco"); - - personService.setPersonProperties("derek", createDefaultProperties("derek", "Derek_", "Hulley_", "dh@dh_", - "alfresco_", rootNodeRef)); - - testProperties(personService.getPerson("derek"), "derek", "Derek_", "Hulley_", "dh@dh_", "alfresco_"); - - personService.setPersonProperties("derek", createDefaultProperties("derek", "Derek", "Hulley", "dh@dh", - "alfresco", rootNodeRef)); - - testProperties(personService.getPerson("derek"), "derek", "Derek", "Hulley", "dh@dh", "alfresco"); - + + testProperties(personService.getPerson("Derek"), "Derek", "Derek", "Hulley", "dh@dh", "alfresco"); + assertEquals(1, personService.getAllPeople().size()); - assertTrue(personService.getAllPeople().contains(personService.getPerson("derek"))); - - personService.deletePerson("derek"); + assertTrue(personService.getAllPeople().contains(personService.getPerson("Derek"))); + assertEquals(personService.personExists("derek"), EqualsHelper.nullSafeEquals(personService.getUserIdentifier("derek"), "Derek")); + assertEquals(personService.personExists("dEREK"), EqualsHelper.nullSafeEquals(personService.getUserIdentifier("dEREK"), "Derek")); + assertEquals(personService.personExists("DEREK"), EqualsHelper.nullSafeEquals(personService.getUserIdentifier("DEREK"), "Derek")); + + personService.deletePerson("Derek"); assertEquals(0, personService.getAllPeople().size()); - + } private void testProperties(NodeRef nodeRef, String userName, String firstName, String lastName, String email, String orgId) { + Map props = nodeService.getProperties(nodeRef); assertEquals(userName, DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(nodeRef, ContentModel.PROP_USERNAME))); assertNotNull(nodeService.getProperty(nodeRef, ContentModel.PROP_HOMEFOLDER)); @@ -287,56 +298,63 @@ public class PersonTest extends BaseSpringTest properties.put(ContentModel.PROP_ORGID, orgId); return properties; } - + public void testCaseSensitive() { - if(personService.getUserNamesAreCaseSensitive()) + + personService + .createPerson(createDefaultProperties("Derek", "Derek", "Hulley", "dh@dh", "alfresco", rootNodeRef)); + + try { - personService.createPerson(createDefaultProperties("Derek", "Derek", "Hulley", "dh@dh", - "alfresco", rootNodeRef)); - - try + NodeRef nodeRef = personService.getPerson("derek"); + if (personService.getUserIdentifier("derek").equals("Derek")) + { + assertNotNull(nodeRef); + } + else { - personService.getPerson("derek"); assertNotNull(null); } - catch (PersonException pe) - { - - } - try - { - personService.getPerson("deRek"); - assertNotNull(null); - } - catch (PersonException pe) - { - - } - try - { - personService.getPerson("DEREK"); - assertNotNull(null); - } - catch (PersonException pe) - { - - } - personService.getPerson("Derek"); } - } - - public void testCaseInsensitive() - { - if(!personService.getUserNamesAreCaseSensitive()) + catch (PersonException pe) { - personService.createPerson(createDefaultProperties("Derek", "Derek", "Hulley", "dh@dh", - "alfresco", rootNodeRef)); - - personService.getPerson("derek"); - personService.getPerson("deRek"); - personService.getPerson("Derek"); - personService.getPerson("DEREK"); + } + try + { + NodeRef nodeRef = personService.getPerson("deRek"); + if (personService.getUserIdentifier("deRek").equals("Derek")) + { + assertNotNull(nodeRef); + } + else + { + assertNotNull(null); + } + } + catch (PersonException pe) + { + + } + try + { + + NodeRef nodeRef = personService.getPerson("DEREK"); + if (personService.getUserIdentifier("DEREK").equals("Derek")) + { + assertNotNull(nodeRef); + } + else + { + assertNotNull(null); + } + } + catch (PersonException pe) + { + + } + personService.getPerson("Derek"); } + } diff --git a/source/java/org/alfresco/repo/service/ServiceDescriptorRegistry.java b/source/java/org/alfresco/repo/service/ServiceDescriptorRegistry.java index d7e846d429..eb018bc77a 100644 --- a/source/java/org/alfresco/repo/service/ServiceDescriptorRegistry.java +++ b/source/java/org/alfresco/repo/service/ServiceDescriptorRegistry.java @@ -44,6 +44,7 @@ import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.cmr.version.VersionService; import org.alfresco.service.cmr.view.ExporterService; import org.alfresco.service.cmr.view.ImporterService; +import org.alfresco.service.cmr.workflow.WorkflowService; import org.alfresco.service.descriptor.DescriptorService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; @@ -313,4 +314,12 @@ public class ServiceDescriptorRegistry { return (ScriptService)getService(SCRIPT_SERVICE); } + + /* (non-Javadoc) + * @see org.alfresco.service.ServiceRegistry#getWorkflowService() + */ + public WorkflowService getWorkflowService() + { + return (WorkflowService)getService(WORKFLOW_SERVICE); + } } diff --git a/source/java/org/alfresco/repo/template/FreeMarkerProcessor.java b/source/java/org/alfresco/repo/template/FreeMarkerProcessor.java index d311575cc9..2ba4994524 100644 --- a/source/java/org/alfresco/repo/template/FreeMarkerProcessor.java +++ b/source/java/org/alfresco/repo/template/FreeMarkerProcessor.java @@ -18,10 +18,17 @@ package org.alfresco.repo.template; import java.io.IOException; import java.io.Writer; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.TemplateException; +import org.alfresco.service.cmr.repository.TemplateImageResolver; +import org.alfresco.service.cmr.repository.TemplateNode; import org.alfresco.service.cmr.repository.TemplateProcessor; import org.apache.log4j.Logger; @@ -32,7 +39,17 @@ import freemarker.template.Template; import freemarker.template.TemplateExceptionHandler; /** - * FreeMarker implementation the template processor interface + * FreeMarker implementation of the template processor interface. + *

+ * Service to process FreeMarker template files loaded from various sources including + * the classpath, repository and directly from a String. + *

+ * The template is processed against a data model generally consisting of a map of + * named objects. FreeMarker can natively handle any POJO objects using standard bean + * notation syntax. It has support for walking List objects. A 'standard' data model + * helper is provided to help generate an object model containing well known objects + * such as the Company Home, User Home and current User nodes. It also provides helpful + * util classes to process Date objects and repository specific custom methods. * * @author Kevin Roast */ @@ -47,9 +64,6 @@ public class FreeMarkerProcessor implements TemplateProcessor /** Pseudo path to String based template */ private static final String PATH = "string://fixed"; - /** FreeMarker processor configuration */ - private Configuration config = null; - /** The permission-safe node service */ private NodeService nodeService; @@ -83,25 +97,21 @@ public class FreeMarkerProcessor implements TemplateProcessor */ private Configuration getConfig() { - if (this.config == null) - { - Configuration config = new Configuration(); - - // setup template cache - config.setCacheStorage(new MruCacheStorage(20, 0)); - - // use our custom loader to find templates on the ClassPath - config.setTemplateLoader(new ClassPathRepoTemplateLoader(nodeService, contentService)); - - // use our custom object wrapper that can deal with QNameMap objects directly - config.setObjectWrapper(new QNameAwareObjectWrapper()); - - // rethrow any exception so we can deal with them - config.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); - - this.config = config; - } - return this.config; + Configuration config = new Configuration(); + + // setup template cache + config.setCacheStorage(new MruCacheStorage(2, 0)); + + // use our custom loader to find templates on the ClassPath + config.setTemplateLoader(new ClassPathRepoTemplateLoader(nodeService, contentService)); + + // use our custom object wrapper that can deal with QNameMap objects directly + config.setObjectWrapper(new QNameAwareObjectWrapper()); + + // rethrow any exception so we can deal with them + config.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); + + return config; } /** @@ -116,6 +126,9 @@ public class FreeMarkerProcessor implements TemplateProcessor { Configuration config = new Configuration(); + // setup template cache + config.setCacheStorage(new MruCacheStorage(2, 0)); + // use our custom loader to load a template directly from a String StringTemplateLoader stringTemplateLoader = new StringTemplateLoader(); stringTemplateLoader.putTemplate(path, template); @@ -243,4 +256,56 @@ public class FreeMarkerProcessor implements TemplateProcessor throw new TemplateException(MSG_ERROR_TEMPLATE_IO, new Object[] {template}, ioerr); } } + + /** + * Create the default data-model available to templates as global objects. + *

+ * 'companyhome' - the Company Home node
+ * 'userhome' - the current user home space node
+ * 'person' - the node representing the current user Person
+ * 'template' - the node representing the template itself (may not be available) + *

+ * Also adds various helper util objects and methods. + * + * @param services ServiceRegistry + * @param person The current user Person Node + * @param companyHome The CompanyHome ref + * @param userHome The User home space ref + * @param template Optional ref to the template itself + * @param resolver Image resolver to resolve icon images etc. + * + * @return A Map of Templatable Node objects and util objects. + */ + public static Map buildDefaultModel( + ServiceRegistry services, + NodeRef person, NodeRef companyHome, NodeRef userHome, NodeRef template, + TemplateImageResolver imageResolver) + { + Map model = new HashMap(16, 1.0f); + + // supply the Company Home space as "companyhome" + model.put("companyhome", new TemplateNode(companyHome, services, imageResolver)); + + // supply the users Home Space as "userhome" + model.put("userhome", new TemplateNode(userHome, services, imageResolver)); + + // supply the current user Node as "person" + model.put("person", new TemplateNode(person, services, imageResolver)); + + // add the template itself as "template" if it comes from content on a node + if (template != null) + { + model.put("template", new TemplateNode(template, services, imageResolver)); + } + + // current date/time is useful to have and isn't supplied by FreeMarker by default + model.put("date", new Date()); + + // add custom method objects + model.put("hasAspect", new HasAspectMethod()); + model.put("message", new I18NMessageMethod()); + model.put("dateCompare", new DateCompareMethod()); + + return model; + } } diff --git a/source/java/org/alfresco/repo/template/NamePathResultsMap.java b/source/java/org/alfresco/repo/template/NamePathResultsMap.java index 3cf8a77267..9641da39af 100644 --- a/source/java/org/alfresco/repo/template/NamePathResultsMap.java +++ b/source/java/org/alfresco/repo/template/NamePathResultsMap.java @@ -28,7 +28,7 @@ import org.alfresco.service.cmr.repository.TemplateNode; * * @author Kevin Roast */ -public final class NamePathResultsMap extends BasePathResultsMap +public class NamePathResultsMap extends BasePathResultsMap { /** * Constructor diff --git a/source/java/org/alfresco/repo/template/NodeSearchResultsMap.java b/source/java/org/alfresco/repo/template/NodeSearchResultsMap.java new file mode 100644 index 0000000000..2ae5213683 --- /dev/null +++ b/source/java/org/alfresco/repo/template/NodeSearchResultsMap.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.template; + +import java.util.List; + +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.TemplateNode; + +/** + * Provides functionality to execute a Lucene search for a single node by NodeRef. + * + * @author Kevin Roast + */ +public class NodeSearchResultsMap extends BaseSearchResultsMap +{ + /** + * Constructor + * + * @param parent The parent TemplateNode to execute searches from + * @param services The ServiceRegistry to use + */ + public NodeSearchResultsMap(TemplateNode parent, ServiceRegistry services) + { + super(parent, services); + } + + /** + * @see org.alfresco.repo.template.BaseTemplateMap#get(java.lang.Object) + */ + public Object get(Object key) + { + TemplateNode result = null; + if (key != null) + { + String ref = key.toString().replace(":", "\\:"); + ref = ref.replace("/", "\\/"); + + List results = query(ref); + + if (results.size() == 1) + { + result = results.get(0); + } + } + return result; + } +} diff --git a/source/java/org/alfresco/repo/template/XPathResultsMap.java b/source/java/org/alfresco/repo/template/XPathResultsMap.java index dcc6a23b47..dc0ae7210e 100644 --- a/source/java/org/alfresco/repo/template/XPathResultsMap.java +++ b/source/java/org/alfresco/repo/template/XPathResultsMap.java @@ -25,7 +25,7 @@ import org.alfresco.service.cmr.repository.TemplateNode; * * @author Kevin Roast */ -public final class XPathResultsMap extends BasePathResultsMap +public class XPathResultsMap extends BasePathResultsMap { /** * Constructor diff --git a/source/java/org/alfresco/repo/version/BaseVersionStoreTest.java b/source/java/org/alfresco/repo/version/BaseVersionStoreTest.java index 93fcc1c0fb..9927d1a5ba 100644 --- a/source/java/org/alfresco/repo/version/BaseVersionStoreTest.java +++ b/source/java/org/alfresco/repo/version/BaseVersionStoreTest.java @@ -26,6 +26,7 @@ import java.util.Map; import org.alfresco.model.ContentModel; import org.alfresco.repo.dictionary.DictionaryDAO; import org.alfresco.repo.dictionary.M2Model; +import org.alfresco.repo.node.archive.NodeArchiveService; import org.alfresco.repo.security.authentication.MutableAuthenticationDao; import org.alfresco.repo.version.common.counter.VersionCounterService; import org.alfresco.repo.version.common.versionlabel.SerialVersionLabelPolicy; @@ -56,6 +57,7 @@ public abstract class BaseVersionStoreTest extends BaseSpringTest protected AuthenticationService authenticationService; protected TransactionService transactionService; protected MutableAuthenticationDao authenticationDAO; + protected NodeArchiveService nodeArchiveService; /* * Data used by tests @@ -136,6 +138,7 @@ public abstract class BaseVersionStoreTest extends BaseSpringTest this.authenticationService = (AuthenticationService)applicationContext.getBean("authenticationService"); this.transactionService = (TransactionService)this.applicationContext.getBean("transactionComponent"); this.authenticationDAO = (MutableAuthenticationDao) applicationContext.getBean("alfDaoImpl"); + this.nodeArchiveService = (NodeArchiveService) applicationContext.getBean("nodeArchiveService"); authenticationService.clearCurrentSecurityContext(); diff --git a/source/java/org/alfresco/repo/version/NodeServiceImpl.java b/source/java/org/alfresco/repo/version/NodeServiceImpl.java index 62b50a957a..0de161ca00 100644 --- a/source/java/org/alfresco/repo/version/NodeServiceImpl.java +++ b/source/java/org/alfresco/repo/version/NodeServiceImpl.java @@ -442,6 +442,15 @@ public class NodeServiceImpl implements NodeService, VersionModel return result; } + /** + * @throws UnsupportedOperationException always + */ + public NodeRef getChildByName(NodeRef nodeRef, QName assocTypeQName, String childName) + { + // This operation is not supported for a verion store + throw new UnsupportedOperationException(MSG_UNSUPPORTED); + } + /** * Simulates the node begin attached ot the root node of the version store. */ diff --git a/source/java/org/alfresco/repo/version/VersionServiceImpl.java b/source/java/org/alfresco/repo/version/VersionServiceImpl.java index 03a111787f..933ebb4578 100644 --- a/source/java/org/alfresco/repo/version/VersionServiceImpl.java +++ b/source/java/org/alfresco/repo/version/VersionServiceImpl.java @@ -373,6 +373,9 @@ public class VersionServiceImpl extends AbstractVersionServiceImpl ContentModel.PROP_VERSION_LABEL, version.getVersionLabel()); + // Invoke the policy behaviour + invokeAfterCreateVersion(nodeRef, version); + // Return the data object representing the newly created version return version; } @@ -384,15 +387,15 @@ public class VersionServiceImpl extends AbstractVersionServiceImpl { VersionHistory versionHistory = null; - if (this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE) == true) + if (this.nodeService.exists(nodeRef) == true) { - NodeRef versionHistoryRef = getVersionHistoryNodeRef(nodeRef); - if (versionHistoryRef != null) - { - versionHistory = buildVersionHistory(versionHistoryRef, nodeRef); - } + NodeRef versionHistoryRef = getVersionHistoryNodeRef(nodeRef); + if (versionHistoryRef != null) + { + versionHistory = buildVersionHistory(versionHistoryRef, nodeRef); + } } - + return versionHistory; } @@ -1078,7 +1081,7 @@ public class VersionServiceImpl extends AbstractVersionServiceImpl // Delete the version history node this.dbNodeService.deleteNode(versionHistoryNodeRef); - if (this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE) == true) + if (this.nodeService.exists(nodeRef) == true && this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE) == true) { // Reset the version label property on the versionable node this.nodeService.setProperty(nodeRef, ContentModel.PROP_VERSION_LABEL, null); diff --git a/source/java/org/alfresco/repo/version/VersionServiceImplTest.java b/source/java/org/alfresco/repo/version/VersionServiceImplTest.java index b04fe1b1a1..ddc677d3db 100644 --- a/source/java/org/alfresco/repo/version/VersionServiceImplTest.java +++ b/source/java/org/alfresco/repo/version/VersionServiceImplTest.java @@ -26,6 +26,7 @@ import org.alfresco.repo.transaction.TransactionUtil; import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.version.Version; import org.alfresco.service.cmr.version.VersionHistory; import org.alfresco.service.cmr.version.VersionServiceException; @@ -609,4 +610,74 @@ public class VersionServiceImplTest extends BaseVersionStoreTest } }); } + + public void testAutoRemovalOfVersionHistory() + { + StoreRef spacesStoreRef = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore"); + NodeRef root = this.dbNodeService.getRootNode(spacesStoreRef); + + HashMap props2 = new HashMap(); + props2.put(ContentModel.PROP_NAME, "test.txt"); + final NodeRef nodeRef = this.dbNodeService.createNode( + root, + ContentModel.ASSOC_CHILDREN, + QName.createQName("{test}MyVersionableNode2"), + ContentModel.TYPE_CONTENT, + props2).getChildRef(); + this.dbNodeService.addAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE, null); + + setComplete(); + endTransaction(); + + TransactionUtil.executeInUserTransaction(this.transactionService, new TransactionUtil.TransactionWork() + { + public Object doWork() throws Exception + { + VersionHistory versionHistory = VersionServiceImplTest.this.versionService.getVersionHistory(nodeRef); + assertNotNull(versionHistory); + assertEquals(1, versionHistory.getAllVersions().size()); + + // Delete the node + VersionServiceImplTest.this.dbNodeService.deleteNode(nodeRef); + + return null; + } + }); + + TransactionUtil.executeInUserTransaction(this.transactionService, new TransactionUtil.TransactionWork() + { + public Object doWork() throws Exception + { + // Get the archived noderef + NodeRef archivedNodeRef = VersionServiceImplTest.this.nodeArchiveService.getArchivedNode(nodeRef); + + // The archived noderef should still have a link to the version history + VersionHistory versionHistory = VersionServiceImplTest.this.versionService.getVersionHistory(archivedNodeRef); + assertNotNull(versionHistory); + assertEquals(1, versionHistory.getAllVersions().size()); + + // Delete the node for good + VersionServiceImplTest.this.dbNodeService.deleteNode(archivedNodeRef); + + return null; + } + }); + + TransactionUtil.executeInUserTransaction(this.transactionService, new TransactionUtil.TransactionWork() + { + public Object doWork() throws Exception + { + // Get the archived noderef + NodeRef archivedNodeRef = VersionServiceImplTest.this.nodeArchiveService.getArchivedNode(nodeRef); + + // Check that the version histories have been deleted + VersionHistory versionHistory12 = VersionServiceImplTest.this.versionService.getVersionHistory(nodeRef); + assertNull(versionHistory12); + VersionHistory versionHistory23 = VersionServiceImplTest.this.versionService.getVersionHistory(archivedNodeRef); + assertNull(versionHistory23); + + return null; + } + }); + } } diff --git a/source/java/org/alfresco/repo/version/VersionServicePolicies.java b/source/java/org/alfresco/repo/version/VersionServicePolicies.java index b04086c1b3..60e01d32ef 100644 --- a/source/java/org/alfresco/repo/version/VersionServicePolicies.java +++ b/source/java/org/alfresco/repo/version/VersionServicePolicies.java @@ -46,11 +46,37 @@ public interface VersionServicePolicies } + /** + * After create version policy interface + * + */ + public interface AfterCreateVersionPolicy extends ClassPolicy + { + /** + * Called after the version has been created + * + * @param versionableNode the node that has been versioned + * @param version the created version + */ + public void afterCreateVersion(NodeRef versionableNode, Version version); + } + /** * On create version policy interface */ public interface OnCreateVersionPolicy extends ClassPolicy { + /** + * Called during the creation of the version to determine what the versioning policy for a + * perticular type may be. + * WARNING: implementing behaviour for this policy effects the versioning behaviour of the + * type the behaviour is registered against. + * + * @param classRef + * @param versionableNode + * @param versionProperties + * @param nodeDetails + */ public void onCreateVersion( QName classRef, NodeRef versionableNode, diff --git a/source/java/org/alfresco/repo/version/VersionableAspect.java b/source/java/org/alfresco/repo/version/VersionableAspect.java index f285d64b3a..4420abf799 100644 --- a/source/java/org/alfresco/repo/version/VersionableAspect.java +++ b/source/java/org/alfresco/repo/version/VersionableAspect.java @@ -29,6 +29,7 @@ import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; import org.alfresco.repo.policy.PolicyScope; 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.NodeService; import org.alfresco.service.cmr.repository.StoreRef; @@ -44,14 +45,16 @@ import org.alfresco.service.namespace.QName; */ public class VersionableAspect implements ContentServicePolicies.OnContentUpdatePolicy, NodeServicePolicies.OnAddAspectPolicy, - NodeServicePolicies.OnRemoveAspectPolicy + NodeServicePolicies.OnRemoveAspectPolicy, + NodeServicePolicies.OnDeleteNodePolicy, + VersionServicePolicies.AfterCreateVersionPolicy { /** The i18n'ized messages */ private static final String MSG_INITIAL_VERSION = "create_version.initial_version"; private static final String MSG_AUTO_VERSION = "create_version.auto_version"; /** Transaction resource key */ - private static final String KEY_INITIAL_VERSION = "initial_version_"; + private static final String KEY_VERSIONED_NODEREFS = "versioned_noderefs"; /** The policy component */ private PolicyComponent policyComponent; @@ -108,6 +111,14 @@ public class VersionableAspect implements ContentServicePolicies.OnContentUpdate QName.createQName(NamespaceService.ALFRESCO_URI, "onRemoveAspect"), ContentModel.ASPECT_VERSIONABLE, new JavaBehaviour(this, "onRemoveAspect", Behaviour.NotificationFrequency.TRANSACTION_COMMIT)); + this.policyComponent.bindClassBehaviour( + QName.createQName(NamespaceService.ALFRESCO_URI, "onDeleteNode"), + ContentModel.ASPECT_VERSIONABLE, + new JavaBehaviour(this, "onDeleteNode", Behaviour.NotificationFrequency.TRANSACTION_COMMIT)); + this.policyComponent.bindClassBehaviour( + QName.createQName(NamespaceService.ALFRESCO_URI, "afterCreateVersion"), + ContentModel.ASPECT_VERSIONABLE, + new JavaBehaviour(this, "afterCreateVersion", Behaviour.NotificationFrequency.EVERY_EVENT)); autoVersionBehaviour = new JavaBehaviour(this, "onContentUpdate", Behaviour.NotificationFrequency.TRANSACTION_COMMIT); this.policyComponent.bindClassBehaviour( @@ -122,6 +133,19 @@ public class VersionableAspect implements ContentServicePolicies.OnContentUpdate new JavaBehaviour(this, "onCopy")); } + /** + * @see org.alfresco.repo.node.NodeServicePolicies.OnDeleteNodePolicy#onDeleteNode(org.alfresco.service.cmr.repository.ChildAssociationRef, boolean) + */ + public void onDeleteNode(ChildAssociationRef childAssocRef, boolean isNodeArchived) + { + if (isNodeArchived == false) + { + // If we are perminantly deleting the node then we need to remove the associated version history + this.versionService.deleteVersionHistory(childAssocRef.getChildRef()); + } + // otherwise we do nothing since we need to hold onto the version history in case the node is restored later + } + /** * OnCopy behaviour implementation for the version aspect. *

@@ -154,7 +178,7 @@ public class VersionableAspect implements ContentServicePolicies.OnContentUpdate */ public void onAddAspect(NodeRef nodeRef, QName aspectTypeQName) { - if (aspectTypeQName.equals(ContentModel.ASPECT_VERSIONABLE) == true) + if (this.nodeService.exists(nodeRef) == true && aspectTypeQName.equals(ContentModel.ASPECT_VERSIONABLE) == true) { boolean initialVersion = true; Boolean value = (Boolean)this.nodeService.getProperty(nodeRef, ContentModel.PROP_INITIAL_VERSION); @@ -166,13 +190,14 @@ public class VersionableAspect implements ContentServicePolicies.OnContentUpdate if (initialVersion == true) { - // Queue create version action - Map versionDetails = new HashMap(1); - versionDetails.put(Version.PROP_DESCRIPTION, I18NUtil.getMessage(MSG_INITIAL_VERSION)); - this.versionService.createVersion(nodeRef, versionDetails); - - // Keep track of the fact that the initial version has been created - AlfrescoTransactionSupport.bindResource(KEY_INITIAL_VERSION + nodeRef.toString(), nodeRef); + Map versionedNodeRefs = (Map)AlfrescoTransactionSupport.getResource(KEY_VERSIONED_NODEREFS); + if (versionedNodeRefs == null || versionedNodeRefs.containsKey(nodeRef) == false) + { + // Queue create version action + Map versionDetails = new HashMap(1); + versionDetails.put(Version.PROP_DESCRIPTION, I18NUtil.getMessage(MSG_INITIAL_VERSION)); + this.versionService.createVersion(nodeRef, versionDetails); + } } } } @@ -191,12 +216,13 @@ public class VersionableAspect implements ContentServicePolicies.OnContentUpdate * * @param nodeRef the node reference */ - public void onContentUpdate(NodeRef nodeRef, boolean newContent) + @SuppressWarnings("unchecked") + public void onContentUpdate(NodeRef nodeRef, boolean newContent) { - if (this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE) == true) - { - // Determine whether we have already created an initial version during this transaction - if (AlfrescoTransactionSupport.getResource(KEY_INITIAL_VERSION + nodeRef.toString()) == null) + if (this.nodeService.exists(nodeRef) == true && this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE) == true) + { + Map versionedNodeRefs = (Map)AlfrescoTransactionSupport.getResource(KEY_VERSIONED_NODEREFS); + if (versionedNodeRefs == null || versionedNodeRefs.containsKey(nodeRef) == false) { // Determine whether the node is auto versionable or not boolean autoVersion = false; @@ -218,22 +244,19 @@ public class VersionableAspect implements ContentServicePolicies.OnContentUpdate } } } - + /** - * Enable the auto version behaviour - * + * @see org.alfresco.repo.version.VersionServicePolicies.OnCreateVersionPolicy#onCreateVersion(org.alfresco.service.namespace.QName, org.alfresco.service.cmr.repository.NodeRef, java.util.Map, org.alfresco.repo.policy.PolicyScope) */ - public void enableAutoVersion() - { - this.autoVersionBehaviour.enable(); - } - - /** - * Disable the auto version behaviour - * - */ - public void disableAutoVersion() - { - this.autoVersionBehaviour.disable(); - } + @SuppressWarnings("unchecked") + public void afterCreateVersion(NodeRef versionableNode, Version version) + { + Map versionedNodeRefs = (Map)AlfrescoTransactionSupport.getResource(KEY_VERSIONED_NODEREFS); + if (versionedNodeRefs == null) + { + versionedNodeRefs = new HashMap(); + AlfrescoTransactionSupport.bindResource(KEY_VERSIONED_NODEREFS, versionedNodeRefs); + } + versionedNodeRefs.put(versionableNode, versionableNode); + } } diff --git a/source/java/org/alfresco/repo/version/common/AbstractVersionServiceImpl.java b/source/java/org/alfresco/repo/version/common/AbstractVersionServiceImpl.java index 450891216b..9f8148ef20 100644 --- a/source/java/org/alfresco/repo/version/common/AbstractVersionServiceImpl.java +++ b/source/java/org/alfresco/repo/version/common/AbstractVersionServiceImpl.java @@ -26,6 +26,7 @@ import org.alfresco.repo.policy.ClassPolicyDelegate; import org.alfresco.repo.policy.PolicyComponent; import org.alfresco.repo.policy.PolicyScope; import org.alfresco.repo.version.VersionServicePolicies; +import org.alfresco.repo.version.VersionServicePolicies.AfterCreateVersionPolicy; import org.alfresco.repo.version.VersionServicePolicies.BeforeCreateVersionPolicy; import org.alfresco.repo.version.VersionServicePolicies.CalculateVersionLabelPolicy; import org.alfresco.repo.version.VersionServicePolicies.OnCreateVersionPolicy; @@ -68,6 +69,7 @@ public abstract class AbstractVersionServiceImpl * Policy delegates */ private ClassPolicyDelegate beforeCreateVersionDelegate; + private ClassPolicyDelegate afterCreateVersionDelegate; private ClassPolicyDelegate onCreateVersionDelegate; private ClassPolicyDelegate calculateVersionLabelDelegate; @@ -108,6 +110,7 @@ public abstract class AbstractVersionServiceImpl { // Register the policies this.beforeCreateVersionDelegate = this.policyComponent.registerClassPolicy(VersionServicePolicies.BeforeCreateVersionPolicy.class); + this.afterCreateVersionDelegate = this.policyComponent.registerClassPolicy(VersionServicePolicies.AfterCreateVersionPolicy.class); this.onCreateVersionDelegate = this.policyComponent.registerClassPolicy(VersionServicePolicies.OnCreateVersionPolicy.class); this.calculateVersionLabelDelegate = this.policyComponent.registerClassPolicy(VersionServicePolicies.CalculateVersionLabelPolicy.class); } @@ -127,6 +130,22 @@ public abstract class AbstractVersionServiceImpl this.beforeCreateVersionDelegate.get(nodeAspectQNames).beforeCreateVersion(nodeRef); } + /** + * Invoke the after create version policy bahaviour + * + * @param nodeRef the nodeRef versioned + * @param version the created version + */ + protected void invokeAfterCreateVersion(NodeRef nodeRef, Version version) + { + // invoke for node type + QName nodeTypeQName = nodeService.getType(nodeRef); + this.afterCreateVersionDelegate.get(nodeTypeQName).afterCreateVersion(nodeRef, version); + // invoke for node aspects + Set nodeAspectQNames = nodeService.getAspects(nodeRef); + this.afterCreateVersionDelegate.get(nodeAspectQNames).afterCreateVersion(nodeRef, version); + } + /** * Invoke the on create version policy behaviour * diff --git a/source/java/org/alfresco/repo/workflow/BPMEngine.java b/source/java/org/alfresco/repo/workflow/BPMEngine.java new file mode 100644 index 0000000000..82cae295b3 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/BPMEngine.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.workflow; + +import org.alfresco.service.cmr.workflow.WorkflowException; +import org.springframework.beans.factory.InitializingBean; + + +/** + * Base functionality for a plug-in BPM Engine + * + * @author davidc + */ +public class BPMEngine implements InitializingBean +{ + private BPMEngineRegistry registry; + private String engineId; + + + /** + * Sets the BPM Engine Registry + * + * @param registry the registry + */ + public void setBPMEngineRegistry(BPMEngineRegistry registry) + { + this.registry = registry; + } + + /** + * Sets the BPM Engine Id + * + * @param engineId the id + */ + public void setEngineId(String engineId) + { + this.engineId = engineId; + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() + */ + public void afterPropertiesSet() throws Exception + { + if (engineId == null || engineId.length() == 0) + { + throw new WorkflowException("Engine Id not specified"); + } + + if (this instanceof WorkflowComponent) + { + registry.registerWorkflowComponent(engineId, (WorkflowComponent)this); + } + if (this instanceof TaskComponent) + { + registry.registerTaskComponent(engineId, (TaskComponent)this); + } + } + + /** + * Construct a global Id for use outside of the engine + * + * @param localId the local engine id + * @return the global id + */ + protected String createGlobalId(String localId) + { + return BPMEngineRegistry.createGlobalId(engineId, localId); + } + + /** + * Construct a local Id from a global Id + * + * @param globalId the global id + * @return the local id + */ + protected String createLocalId(String globalId) + { + return BPMEngineRegistry.getLocalId(globalId); + } +} diff --git a/source/java/org/alfresco/repo/workflow/BPMEngineRegistry.java b/source/java/org/alfresco/repo/workflow/BPMEngineRegistry.java new file mode 100644 index 0000000000..fa2ebfaca7 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/BPMEngineRegistry.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.workflow; + +import java.util.HashMap; +import java.util.Map; + +import org.alfresco.service.cmr.workflow.WorkflowException; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + + +/** + * BPM Engine Registry + * + * Responsible for managing the list of registered BPM Engines for the + * following components: + * + * - Workflow Component + * - Task Component + * + * @author davidc + */ +public class BPMEngineRegistry +{ + /** ID seperator used in global Ids */ + private static final String ID_SEPERATOR = "$"; + private static final String ID_SEPERATOR_REGEX = "\\$"; + + /** Logging support */ + private static Log logger = LogFactory.getLog("org.alfresco.repo.workflow"); + + private Map workflowComponents; + private Map taskComponents; + + + /** + * Construct + */ + public BPMEngineRegistry() + { + workflowComponents = new HashMap(); + taskComponents = new HashMap(); + } + + /** + * Register a BPM Engine Workflow Component + * + * @param engineId engine id + * @param engine implementing engine + */ + public void registerWorkflowComponent(String engineId, WorkflowComponent engine) + { + if (workflowComponents.containsKey(engineId)) + { + throw new WorkflowException("Workflow Component already registered for engine id '" + engineId + "'"); + } + workflowComponents.put(engineId, engine); + + if (logger.isInfoEnabled()) + logger.info("Registered Workflow Component '" + engineId + "' (" + engine.getClass() + ")"); + } + + /** + * Gets all registered Workflow Components + * + * @return array of engine ids + */ + public String[] getWorkflowComponents() + { + return workflowComponents.keySet().toArray(new String[workflowComponents.keySet().size()]); + } + + /** + * Gets a specific BPM Engine Workflow Component + * + * @param engineId engine id + * @return the Workflow Component + */ + public WorkflowComponent getWorkflowComponent(String engineId) + { + return workflowComponents.get(engineId); + } + + /** + * Register a BPM Engine Task Component + * + * @param engineId engine id + * @param engine implementing engine + */ + public void registerTaskComponent(String engineId, TaskComponent engine) + { + if (taskComponents.containsKey(engineId)) + { + throw new WorkflowException("Task Component already registered for engine id '" + engineId + "'"); + } + taskComponents.put(engineId, engine); + + if (logger.isInfoEnabled()) + logger.info("Registered Task Component '" + engineId + "' (" + engine.getClass() + ")"); + } + + /** + * Gets all registered Task Components + * + * @return array of engine ids + */ + public String[] getTaskComponents() + { + return taskComponents.keySet().toArray(new String[taskComponents.keySet().size()]); + } + + /** + * Gets a specific BPM Engine Task Component + * + * @param engineId engine id + * @return the Workflow Component + */ + public TaskComponent getTaskComponent(String engineId) + { + return taskComponents.get(engineId); + } + + + // + // BPM Engine Id support + // + + /** + * Construct a global Id + * + * @param engineId engine id + * @param localId engine local id + * @return the global id + */ + public static String createGlobalId(String engineId, String localId) + { + return engineId + ID_SEPERATOR + localId; + } + + /** + * Break apart a global id into its engine and local ids + * + * @param globalId the global id + * @return array containing engine id and global id in that order + */ + public static String[] getGlobalIdParts(String globalId) + { + String[] parts = globalId.split(ID_SEPERATOR_REGEX); + if (parts.length != 2) + { + throw new WorkflowException("Invalid Global Id '" + globalId + "'"); + } + return parts; + } + + /** + * Get the engine id from a global id + * + * @param globalId the global id + * @return the engine id + */ + public static String getEngineId(String globalId) + { + return getGlobalIdParts(globalId)[0]; + } + + /** + * Get the local id from a global id + * + * @param globalId the global id + * @return the local id + */ + public static String getLocalId(String globalId) + { + return getGlobalIdParts(globalId)[1]; + } + +} diff --git a/source/java/org/alfresco/repo/workflow/StartWorkflowActionExecuter.java b/source/java/org/alfresco/repo/workflow/StartWorkflowActionExecuter.java new file mode 100644 index 0000000000..0aa6c80b6d --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/StartWorkflowActionExecuter.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.workflow; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.action.ParameterDefinitionImpl; +import org.alfresco.repo.action.executer.ActionExecuterAbstractBase; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ParameterDefinition; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.workflow.WorkflowDefinition; +import org.alfresco.service.cmr.workflow.WorkflowService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; + +/** + * Simple workflow action executor + * + * @author Roy Wetherall + */ +public class StartWorkflowActionExecuter extends ActionExecuterAbstractBase +{ + public static final String NAME = "start-workflow"; + + public static final String PARAM_WORKFLOW_NAME = "workflowName"; + + // action dependencies + private NamespaceService namespaceService; + private WorkflowService workflowService; + private NodeService nodeService; + + + /** + * @param namespaceService + */ + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + /** + * @param nodeService + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * @param workflowService + */ + public void setWorkflowService(WorkflowService workflowService) + { + this.workflowService = workflowService; + } + + + /* (non-Javadoc) + * @see org.alfresco.repo.action.ParameterizedItemAbstractBase#getAdhocPropertiesAllowed() + */ + @Override + protected boolean getAdhocPropertiesAllowed() + { + return true; + } + + + /* (non-Javadoc) + * @see org.alfresco.repo.action.ParameterizedItemAbstractBase#addParameterDefinitions(java.util.List) + */ + @Override + protected void addParameterDefinitions(List paramList) + { + paramList.add(new ParameterDefinitionImpl(PARAM_WORKFLOW_NAME, DataTypeDefinition.TEXT, false, getParamDisplayLabel(PARAM_WORKFLOW_NAME))); + // TODO: Start Task Template parameter + } + + + /* (non-Javadoc) + * @see org.alfresco.repo.action.executer.ActionExecuterAbstractBase#executeImpl(org.alfresco.service.cmr.action.Action, org.alfresco.service.cmr.repository.NodeRef) + */ + @Override + protected void executeImpl(Action ruleAction, NodeRef actionedUponNodeRef) + { + // retrieve workflow definition + String workflowName = (String)ruleAction.getParameterValue(PARAM_WORKFLOW_NAME); + WorkflowDefinition def = workflowService.getDefinitionByName(workflowName); + + // create workflow package to contain actioned upon node + NodeRef workflowPackage = (NodeRef)ruleAction.getParameterValue(WorkflowModel.ASSOC_PACKAGE.toPrefixString(namespaceService)); + workflowPackage = workflowService.createPackage(workflowPackage); + ChildAssociationRef childAssoc = nodeService.getPrimaryParent(actionedUponNodeRef); + nodeService.addChild(workflowPackage, actionedUponNodeRef, ContentModel.ASSOC_CONTAINS, childAssoc.getQName()); + + // build map of workflow start task parameters + Map paramValues = ruleAction.getParameterValues(); + Map workflowParameters = new HashMap(); + workflowParameters.put(WorkflowModel.ASSOC_PACKAGE, workflowPackage); + for (Map.Entry entry : paramValues.entrySet()) + { + if (!entry.getKey().equals(PARAM_WORKFLOW_NAME)) + { + QName qname = QName.createQName(entry.getKey(), namespaceService); + Serializable value = entry.getValue(); + workflowParameters.put(qname, value); + } + } + + // start the workflow + workflowService.startWorkflow(def.id, workflowParameters); + } + +} diff --git a/source/java/org/alfresco/repo/workflow/StartWorkflowActionExecuterTest.java b/source/java/org/alfresco/repo/workflow/StartWorkflowActionExecuterTest.java new file mode 100644 index 0000000000..c9e271362a --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/StartWorkflowActionExecuterTest.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.workflow; + +import java.util.Date; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.action.ActionImpl; +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.BaseSpringTest; +import org.alfresco.util.GUID; + +/** + * Add features action execution test + * + * @author Roy Wetherall + */ +public class StartWorkflowActionExecuterTest extends BaseSpringTest +{ + private NodeService nodeService; + private NamespaceService namespaceService; + private PersonService personService; + private NodeRef rootNodeRef; + private NodeRef nodeRef; + private StartWorkflowActionExecuter executer; + + + /** + * Called at the begining of all tests + */ + @Override + protected void onSetUpInTransaction() throws Exception + { + this.nodeService = (NodeService)this.applicationContext.getBean("nodeService"); + this.namespaceService = (NamespaceService)this.applicationContext.getBean("namespaceService"); + this.personService = (PersonService)this.applicationContext.getBean("personService"); + + AuthenticationComponent authenticationComponent = (AuthenticationComponent)applicationContext.getBean("authenticationComponent"); + authenticationComponent.setCurrentUser(authenticationComponent.getSystemUserName()); + + // Create the store and get the root node + rootNodeRef = nodeService.getRootNode(new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "spacesStore")); + this.nodeRef = this.nodeService.createNode( + this.rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName("{test}testnode"), + ContentModel.TYPE_CONTENT).getChildRef(); + + // Get the executer instance + this.executer = (StartWorkflowActionExecuter)this.applicationContext.getBean(StartWorkflowActionExecuter.NAME); + } + + /** + * Test execution + */ + public void testExecution() + { + // Execute the action + ActionImpl action = new ActionImpl(null, GUID.generate(), StartWorkflowActionExecuter.NAME, null); + action.setParameterValue(StartWorkflowActionExecuter.PARAM_WORKFLOW_NAME, "jbpm://wf:review"); + action.setParameterValue(WorkflowModel.PROP_REVIEW_DUE_DATE.toPrefixString(namespaceService), new Date()); + NodeRef reviewer = personService.getPerson("admin"); + action.setParameterValue(WorkflowModel.ASSOC_REVIEWER.toPrefixString(namespaceService), reviewer); + executer.execute(action, this.nodeRef); + } +} diff --git a/source/java/org/alfresco/repo/workflow/TaskComponent.java b/source/java/org/alfresco/repo/workflow/TaskComponent.java new file mode 100644 index 0000000000..4836d99cc1 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/TaskComponent.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.workflow; + +import java.io.Serializable; +import java.util.List; +import java.util.Map; + +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.workflow.WorkflowTask; +import org.alfresco.service.cmr.workflow.WorkflowTaskState; +import org.alfresco.service.namespace.QName; + + +/** + * SPI to be implemented by a BPM Engine that provides Task management. + * + * @author davidc + */ +public interface TaskComponent +{ + + /** + * Gets a Task by unique Id + * + * @param taskId the task id + * @return the task + */ + public WorkflowTask getTaskById(String taskId); + + /** + * Gets all tasks assigned to the specified authority + * + * @param authority the authority + * @param state filter by specified workflow task state + * @return the list of assigned tasks + */ + public List getAssignedTasks(String authority, WorkflowTaskState state); + + /** + * Gets the pooled tasks available to the specified authority + * + * @param authority the authority + * @return the list of pooled tasks + */ + public List getPooledTasks(List authorities); + + /** + * Update the Properties and Associations of a Task + * + * @param taskId the task id to update + * @param properties the map of properties to set on the task (or null, if none to set) + * @param add the map of items to associate with the task (or null, if none to add) + * @param remove the map of items to dis-associate with the task (or null, if none to remove) + * @return the update task + */ + public WorkflowTask updateTask(String taskId, Map properties, Map> add, Map> remove); + + /** + * Start the specified Task + * + * Note: this is an optional task operation. It may be used to track + * when work started on a task as well as resume a suspended task. + * + * @param taskId the task to start + * @return the updated task + */ + public WorkflowTask startTask(String taskId); + + /** + * Suspend the specified Task + * + * @param taskId + * @return the update task + */ + public WorkflowTask suspendTask(String taskId); + + /** + * End the Task (i.e. complete the task) + * + * @param taskId the task id to end + * @param transition the task transition to take on completion (or null, for the default transition) + * @return the updated task + */ + public WorkflowTask endTask(String taskId, String transitionId); + +} + diff --git a/source/java/org/alfresco/repo/workflow/WorkflowComponent.java b/source/java/org/alfresco/repo/workflow/WorkflowComponent.java new file mode 100644 index 0000000000..ea0065cc5c --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/WorkflowComponent.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.workflow; + +import java.io.InputStream; +import java.io.Serializable; +import java.util.List; +import java.util.Map; + +import org.alfresco.service.cmr.workflow.WorkflowDefinition; +import org.alfresco.service.cmr.workflow.WorkflowDeployment; +import org.alfresco.service.cmr.workflow.WorkflowInstance; +import org.alfresco.service.cmr.workflow.WorkflowPath; +import org.alfresco.service.cmr.workflow.WorkflowTask; +import org.alfresco.service.namespace.QName; + + +/** + * SPI to be implemented by a BPM Engine that provides Workflow instance management. + * + * @author davidc + */ +public interface WorkflowComponent +{ + + // + // Workflow Definition Support + // + + + /** + * Deploy a Workflow Definition + * + * @param workflowDefinition the content object containing the definition + * @param mimetype (optional) the mime type of the workflow definition + * @return workflow deployment descriptor + */ + public WorkflowDeployment deployDefinition(InputStream workflowDefinition, String mimetype); + + /** + * Is the specified Workflow Definition already deployed? + * + * Note: the notion of "already deployed" may differ between bpm engines. For example, + * different versions of the same process may be considered equal. + * + * @param workflowDefinition the definition to check + * @param mimetype the mimetype of the definition + * @return true => already deployed + */ + public boolean isDefinitionDeployed(InputStream workflowDefinition, String mimetype); + + /** + * Undeploy an exisiting Workflow Definition + * + * TODO: Determine behaviour when "in-flight" workflow instances exist + * + * @param workflowDefinitionId the id of the definition to undeploy + */ + public void undeployDefinition(String workflowDefinitionId); + + /** + * Gets all deployed Workflow Definitions + * + * @return the deployed workflow definitions + */ + public List getDefinitions(); + + /** + * Gets a Workflow Definition by unique Id + * + * @param workflowDefinitionId the workflow definition id + * @return the deployed workflow definition + */ + public WorkflowDefinition getDefinitionById(String workflowDefinitionId); + + /** + * Gets a Workflow Definition by unique name + * + * @param workflowName workflow name e.g. jbpm://review + * @return the deployed workflow definition + */ + public WorkflowDefinition getDefinitionByName(String workflowName); + + + // + // Workflow Instance Support + // + + + /** + * Start a Workflow Instance + * + * @param workflowDefinitionId the workflow definition id + * @param parameters the initial set of parameters used to populate the "Start Task" properties + * @return the initial workflow path + */ + public WorkflowPath startWorkflow(String workflowDefinitionId, Map parameters); + + /** + * Gets all "in-flight" workflow instances of the specified Workflow Definition + * + * @param workflowDefinitionId the workflow definition id + * @return the list of "in-fligth" workflow instances + */ + public List getActiveWorkflows(String workflowDefinitionId); + + /** + * Gets all Paths for the specified Workflow instance + * + * @param workflowId workflow instance id + * @return the list of workflow paths + */ + public List getWorkflowPaths(String workflowId); + + /** + * Cancel an "in-fligth" Workflow instance + * + * @param workflowId the workflow instance to cancel + * @return an updated representation of the workflow instance + */ + public WorkflowInstance cancelWorkflow(String workflowId); + + /** + * Signal the transition from one Workflow Node to another within an "in-flight" + * process. + * + * @param pathId the workflow path to signal on + * @param transition the transition to follow (or null, for the default transition) + * @return the updated workflow path + */ + public WorkflowPath signal(String pathId, String transitionId); + + /** + * Gets all Tasks associated with the specified path + * + * @param pathId the path id + * @return the list of associated tasks + */ + public List getTasksForWorkflowPath(String pathId); + +} + diff --git a/source/java/org/alfresco/repo/workflow/WorkflowDeployer.java b/source/java/org/alfresco/repo/workflow/WorkflowDeployer.java new file mode 100644 index 0000000000..e0020f37c8 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/WorkflowDeployer.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.workflow; + +import java.util.List; +import java.util.Properties; + +import javax.transaction.UserTransaction; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.service.cmr.view.ImporterException; +import org.alfresco.service.cmr.workflow.WorkflowDeployment; +import org.alfresco.service.cmr.workflow.WorkflowException; +import org.alfresco.service.cmr.workflow.WorkflowService; +import org.alfresco.service.transaction.TransactionService; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.core.io.ClassPathResource; + + +/** + * Alfresco bootstrap Process deployment. + * + * @author davidc + */ +public class WorkflowDeployer implements ApplicationListener +{ + // Logging support + private static Log logger = LogFactory.getLog("org.alfresco.repo.workflow"); + + // Workflow Definition Properties (used in setWorkflowDefinitions) + public static final String ENGINE_ID = "engineId"; + public static final String LOCATION = "location"; + public static final String MIMETYPE = "mimetype"; + public static final String REDEPLOY = "redeploy"; + + // Dependencies + private TransactionService transactionService; + private WorkflowService workflowService; + private AuthenticationComponent authenticationComponent; + private List workflowDefinitions; + + + /** + * Sets the Transaction Service + * + * @param userTransaction the transaction service + */ + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + /** + * Sets the namespace service + * + * @param namespaceService the namespace service + */ + public void setWorkflowService(WorkflowService workflowService) + { + this.workflowService = workflowService; + } + + /** + * Set the authentication component + * + * @param authenticationComponent + */ + public void setAuthenticationComponent(AuthenticationComponent authenticationComponent) + { + this.authenticationComponent = authenticationComponent; + } + + /** + * Sets the Workflow Definitions + * + * @param workflowDefinitions + */ + public void setWorkflowDefinitions(List workflowDefinitions) + { + this.workflowDefinitions = workflowDefinitions; + } + + /** + * Deploy the Workflow Definitions + */ + public void deploy() + { + if (transactionService == null) + { + throw new ImporterException("Transaction Service must be provided"); + } + if (authenticationComponent == null) + { + throw new ImporterException("Authentication Component must be provided"); + } + if (workflowService == null) + { + throw new ImporterException("Workflow Service must be provided"); + } + + UserTransaction userTransaction = transactionService.getUserTransaction(); + authenticationComponent.setSystemUserAsCurrentUser(); + + try + { + userTransaction.begin(); + + // bootstrap the workflow definitions + if (workflowDefinitions != null) + { + for (Properties workflowDefinition : workflowDefinitions) + { + // retrieve workflow specification + String engineId = workflowDefinition.getProperty(ENGINE_ID); + if (engineId == null || engineId.length() == 0) + { + throw new WorkflowException("Workflow Engine Id must be provided"); + } + String location = workflowDefinition.getProperty(LOCATION); + if (location == null || location.length() == 0) + { + throw new WorkflowException("Workflow definition location must be provided"); + } + Boolean redeploy = Boolean.valueOf(workflowDefinition.getProperty(REDEPLOY)); + String mimetype = workflowDefinition.getProperty(MIMETYPE); + + // retrieve input stream on workflow definition + ClassPathResource workflowResource = new ClassPathResource(location); + + // deploy workflow definition + if (!redeploy && workflowService.isDefinitionDeployed(engineId, workflowResource.getInputStream(), mimetype)) + { + if (logger.isDebugEnabled()) + logger.debug("Workflow deployer: Definition '" + location + "' already deployed"); + } + else + { + WorkflowDeployment deployment = workflowService.deployDefinition(engineId, workflowResource.getInputStream(), mimetype); + if (logger.isInfoEnabled()) + logger.info("Workflow deployer: Deployed process definition '" + deployment.definition.title + "' (version " + deployment.definition.version + ") from '" + location + "' with " + deployment.problems.length + " problems"); + } + } + } + + userTransaction.commit(); + } + catch(Throwable e) + { + // rollback the transaction + try { if (userTransaction != null) {userTransaction.rollback();} } catch (Exception ex) {} + try {authenticationComponent.clearCurrentSecurityContext(); } catch (Exception ex) {} + throw new AlfrescoRuntimeException("Workflow deployment failed", e); + } + finally + { + authenticationComponent.clearCurrentSecurityContext(); + } + } + + /* + * (non-Javadoc) + * @see org.springframework.context.ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent) + */ + public void onApplicationEvent(ApplicationEvent event) + { + if (event instanceof ContextRefreshedEvent) + { + deploy(); + } + } + +} diff --git a/source/java/org/alfresco/repo/workflow/WorkflowModel.java b/source/java/org/alfresco/repo/workflow/WorkflowModel.java new file mode 100644 index 0000000000..9f72746f5c --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/WorkflowModel.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.workflow; + +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; + + +/** + * Workflow Model Constants + */ +public interface WorkflowModel +{ + + // + // Base Business Process Management Definitions + // + + // task constants + static final QName TYPE_TASK = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "task"); + static final QName PROP_TASK_ID = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "taskId"); + static final QName PROP_START_DATE = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "startDate"); + static final QName PROP_DUE_DATE = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "dueDate"); + static final QName PROP_COMPLETION_DATE = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "completionDate"); + static final QName PROP_PRIORITY = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "priority"); + static final QName PROP_STATUS = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "status"); + static final QName PROP_PERCENT_COMPLETE = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "percentComplete"); + static final QName PROP_COMPLETED_ITEMS = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "completedItems"); + static final QName ASSOC_POOLED_ACTORS = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "pooledActors"); + + // workflow task contstants + static final QName TYPE_WORKFLOW_TASK = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "workflowTask"); + static final QName PROP_CONTEXT = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "context"); + static final QName PROP_OUTCOME = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "outcome"); + static final QName PROP_PACKAGE_ACTION_GROUP = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "packageActionGroup"); + static final QName PROP_PACKAGE_ITEM_ACTION_GROUP = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "packageItemActionGroup"); + static final QName ASSOC_PACKAGE = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "package"); + + // workflow package + static final QName ASPECT_WORKFLOW_PACKAGE = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "workflowPackage"); + static final QName PROP_WORKFLOW_DEFINITION_ID = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "workflowDefinitionId"); + static final QName PROP_WORKFLOW_DEFINITION_NAME = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "workflowDefinitionName"); + static final QName PROP_WORKFLOW_INSTANCE_ID = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "workflowInstanceId"); + + + // + // Workflow Models + // + + // review & approve + static final QName TYPE_SUBMITREVIEW_TASK = QName.createQName(NamespaceService.WORKFLOW_MODEL_1_0_URI, "submitReviewTask"); + static final QName PROP_REVIEW_PRIORITY = QName.createQName(NamespaceService.WORKFLOW_MODEL_1_0_URI, "reviewPriority"); + static final QName PROP_REVIEW_DUE_DATE = QName.createQName(NamespaceService.WORKFLOW_MODEL_1_0_URI, "reviewDueDate"); + static final QName ASSOC_REVIEWER = QName.createQName(NamespaceService.WORKFLOW_MODEL_1_0_URI, "reviewer"); + +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/workflow/WorkflowPackageComponent.java b/source/java/org/alfresco/repo/workflow/WorkflowPackageComponent.java new file mode 100644 index 0000000000..8a4675b3d2 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/WorkflowPackageComponent.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.workflow; + +import org.alfresco.service.cmr.repository.NodeRef; + + +/** + * Contract for managing Workflow Packages. A package is a container + * of Content that's routed through a Workflow. + * + * @author davidc + */ +public interface WorkflowPackageComponent +{ + + /** + * Create a Workflow Package (a container of content to route through the Workflow). + * + * If an existing container is supplied, it's supplemented with the workflow package aspect. + * + * @param container (optional) a pre-created container (e.g. folder, versioned folder or layered folder) + * @return the workflow package + */ + public NodeRef createPackage(NodeRef container); + + // TODO: Support for finding packages via meta-data of WorkflowPackage aspect + +} diff --git a/source/java/org/alfresco/repo/workflow/WorkflowPackageImpl.java b/source/java/org/alfresco/repo/workflow/WorkflowPackageImpl.java new file mode 100644 index 0000000000..3f15a29649 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/WorkflowPackageImpl.java @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.workflow; + +import java.util.ArrayList; +import java.util.List; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.importer.ImporterBootstrap; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.model.FileInfo; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.cmr.workflow.WorkflowException; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.GUID; + + +/** + * Alfresco implementation of Workflow Package where the package is stored + * within the Alfresco Repository. + * + * @author davidc + */ +public class WorkflowPackageImpl implements WorkflowPackageComponent +{ + private final static String PACKAGE_FOLDER = "Workflow Packages"; + + // service dependencies + private ImporterBootstrap bootstrap; + private SearchService searchService; + private NodeService nodeService; + private NamespaceService namespaceService; + private FileFolderService fileFolderService; + private NodeRef systemWorkflowContainer = null; + + + /** + * @param bootstrap the importer bootstrap for the store to place workflow items into + */ + public void setImporterBootstrap(ImporterBootstrap bootstrap) + { + this.bootstrap = bootstrap; + } + + /** + * @param fileFolderService file folder service + */ + public void setFileFolderService(FileFolderService fileFolderService) + { + this.fileFolderService = fileFolderService; + } + + /** + * @param searchService search service + */ + public void setSearchService(SearchService searchService) + { + this.searchService = searchService; + } + + /** + * @param nodeService node service + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * @param namespaceService namespace service + */ + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + + /* (non-Javadoc) + * @see org.alfresco.repo.workflow.WorkflowPackageComponent#createPackage(org.alfresco.service.cmr.repository.NodeRef) + */ + public NodeRef createPackage(NodeRef container) + { + // create a container, if one is not specified + if (container == null) + { + // create simple folder in workflow system folder + NodeRef system = getSystemWorkflowContainer(); + + // TODO: Consider structuring this folder, if number of children becomes an issue + List folders = new ArrayList(); + folders.add(PACKAGE_FOLDER); + folders.add(GUID.generate()); + FileInfo containerFolder = fileFolderService.makeFolders(system, folders, ContentModel.TYPE_FOLDER); + container = containerFolder.getNodeRef(); + } + + // attach workflow package + if (nodeService.hasAspect(container, WorkflowModel.ASPECT_WORKFLOW_PACKAGE)) + { + throw new WorkflowException("Container '" + container + "' is already a workflow package."); + } + nodeService.addAspect(container, WorkflowModel.ASPECT_WORKFLOW_PACKAGE, null); + + // return container + return container; + } + + + /** + * Gets the system workflow container for storing workflow related items + * + * @return the system workflow container + */ + private NodeRef getSystemWorkflowContainer() + { + if (systemWorkflowContainer == null) + { + NodeRef systemContainer = findSystemContainer(); + systemWorkflowContainer = findSystemWorkflowContainer(systemContainer); + if (systemWorkflowContainer == null) + { + throw new WorkflowException("Unable to find system workflow folder - does not exist."); + } + } + return systemWorkflowContainer; + } + + + /** + * Finds the system workflow container + * + * @param systemContainer the system container + * @return the system workflow container + */ + private NodeRef findSystemWorkflowContainer(NodeRef systemContainer) + { + String path = bootstrap.getConfiguration().getProperty("system.workflow_container.childname"); + if (path == null) + { + throw new WorkflowException("Unable to locate workflow system container - path not specified"); + } + List nodeRefs = searchService.selectNodes(systemContainer, path, null, namespaceService, false); + if (nodeRefs != null && nodeRefs.size() > 0) + { + systemWorkflowContainer = nodeRefs.get(0); + } + return systemWorkflowContainer; + } + + + /** + * Finds the system container + * + * @return the system container + */ + private NodeRef findSystemContainer() + { + String path = bootstrap.getConfiguration().getProperty("system.system_container.childname"); + if (path == null) + { + throw new WorkflowException("Unable to locate system container - path not specified"); + } + NodeRef root = nodeService.getRootNode(bootstrap.getStoreRef()); + List nodeRefs = searchService.selectNodes(root, path, null, namespaceService, false); + if (nodeRefs == null || nodeRefs.size() == 0) + { + throw new WorkflowException("Unable to locate system container - path not found"); + } + return nodeRefs.get(0); + } + + + /** + * Creates the System Workflow Container + * + * @return the system workflow container + */ + public NodeRef createSystemWorkflowContainer() + { + NodeRef systemContainer = findSystemContainer(); + NodeRef systemWorkflowContainer = findSystemWorkflowContainer(systemContainer); + if (systemWorkflowContainer == null) + { + String name = bootstrap.getConfiguration().getProperty("system.workflow_container.childname"); + QName qname = QName.createQName(name, namespaceService); + ChildAssociationRef childRef = nodeService.createNode(systemContainer, ContentModel.ASSOC_CHILDREN, qname, ContentModel.TYPE_FOLDER); + systemWorkflowContainer = childRef.getChildRef(); + } + return systemWorkflowContainer; + } + +} diff --git a/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java b/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java new file mode 100644 index 0000000000..bdaac4c483 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java @@ -0,0 +1,330 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.workflow; + +import java.io.InputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.workflow.WorkflowDefinition; +import org.alfresco.service.cmr.workflow.WorkflowDeployment; +import org.alfresco.service.cmr.workflow.WorkflowException; +import org.alfresco.service.cmr.workflow.WorkflowInstance; +import org.alfresco.service.cmr.workflow.WorkflowPath; +import org.alfresco.service.cmr.workflow.WorkflowService; +import org.alfresco.service.cmr.workflow.WorkflowTask; +import org.alfresco.service.cmr.workflow.WorkflowTaskState; +import org.alfresco.service.namespace.QName; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + + +/** + * Default Alfresco Workflow Service whose implementation is backed by registered + * BPM Engine plug-in components. + * + * @author davidc + */ +public class WorkflowServiceImpl implements WorkflowService +{ + // Logging support + private static Log logger = LogFactory.getLog("org.alfresco.repo.workflow"); + + // Dependent services + private BPMEngineRegistry registry; + private WorkflowPackageComponent workflowPackageComponent; + + + /** + * Sets the BPM Engine Registry + * + * @param registry bpm engine registry + */ + public void setBPMEngineRegistry(BPMEngineRegistry registry) + { + this.registry = registry; + } + + /** + * Sets the Workflow Package Component + * + * @param workflowPackage workflow package component + */ + public void setWorkflowPackageComponent(WorkflowPackageComponent workflowPackageComponent) + { + this.workflowPackageComponent = workflowPackageComponent; + } + + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.workflow.WorkflowService#deployDefinition(java.lang.String, java.io.InputStream, java.lang.String) + */ + public WorkflowDeployment deployDefinition(String engineId, InputStream workflowDefinition, String mimetype) + { + WorkflowComponent component = getWorkflowComponent(engineId); + WorkflowDeployment deployment = component.deployDefinition(workflowDefinition, mimetype); + + if (logger.isDebugEnabled() && deployment.problems.length > 0) + { + for (String problem : deployment.problems) + { + logger.debug("Workflow definition '" + deployment.definition.title + "' problem: " + problem); + } + } + + return deployment; + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.workflow.WorkflowService#isDefinitionDeployed(java.lang.String, java.io.InputStream, java.lang.String) + */ + public boolean isDefinitionDeployed(String engineId, InputStream workflowDefinition, String mimetype) + { + WorkflowComponent component = getWorkflowComponent(engineId); + return component.isDefinitionDeployed(workflowDefinition, mimetype); + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.workflow.WorkflowService#deployDefinition(org.alfresco.service.cmr.repository.NodeRef) + */ + public WorkflowDeployment deployDefinition(NodeRef definitionContent) + { + // TODO + throw new UnsupportedOperationException(); + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.workflow.WorkflowService#undeployDefinition(java.lang.String) + */ + public void undeployDefinition(String workflowDefinitionId) + { + String engineId = BPMEngineRegistry.getEngineId(workflowDefinitionId); + WorkflowComponent component = getWorkflowComponent(engineId); + component.undeployDefinition(workflowDefinitionId); + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.workflow.WorkflowService#getDefinitions() + */ + public List getDefinitions() + { + List definitions = new ArrayList(10); + String[] ids = registry.getWorkflowComponents(); + for (String id: ids) + { + WorkflowComponent component = registry.getWorkflowComponent(id); + definitions.addAll(component.getDefinitions()); + } + return Collections.unmodifiableList(definitions); + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.workflow.WorkflowService#getDefinitionById(java.lang.String) + */ + public WorkflowDefinition getDefinitionById(String workflowDefinitionId) + { + String engineId = BPMEngineRegistry.getEngineId(workflowDefinitionId); + WorkflowComponent component = getWorkflowComponent(engineId); + return component.getDefinitionById(workflowDefinitionId); + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.workflow.WorkflowService#getDefinitionByName(java.lang.String) + */ + public WorkflowDefinition getDefinitionByName(String workflowName) + { + String engineId = BPMEngineRegistry.getEngineId(workflowName); + WorkflowComponent component = getWorkflowComponent(engineId); + return component.getDefinitionByName(workflowName); + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.workflow.WorkflowService#startWorkflow(java.lang.String, java.util.Map) + */ + public WorkflowPath startWorkflow(String workflowDefinitionId, Map parameters) + { + String engineId = BPMEngineRegistry.getEngineId(workflowDefinitionId); + WorkflowComponent component = getWorkflowComponent(engineId); + return component.startWorkflow(workflowDefinitionId, parameters); + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.workflow.WorkflowService#startWorkflowFromTemplate(org.alfresco.service.cmr.repository.NodeRef) + */ + public WorkflowPath startWorkflowFromTemplate(NodeRef templateDefinition) + { + // TODO + throw new UnsupportedOperationException(); + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.workflow.WorkflowService#getActiveWorkflows(java.lang.String) + */ + public List getActiveWorkflows(String workflowDefinitionId) + { + String engineId = BPMEngineRegistry.getEngineId(workflowDefinitionId); + WorkflowComponent component = getWorkflowComponent(engineId); + return component.getActiveWorkflows(workflowDefinitionId); + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.workflow.WorkflowService#getWorkflowPaths(java.lang.String) + */ + public List getWorkflowPaths(String workflowId) + { + String engineId = BPMEngineRegistry.getEngineId(workflowId); + WorkflowComponent component = getWorkflowComponent(engineId); + return component.getWorkflowPaths(workflowId); + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.workflow.WorkflowService#cancelWorkflow(java.lang.String) + */ + public WorkflowInstance cancelWorkflow(String workflowId) + { + String engineId = BPMEngineRegistry.getEngineId(workflowId); + WorkflowComponent component = getWorkflowComponent(engineId); + return component.cancelWorkflow(workflowId); + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.workflow.WorkflowService#signal(java.lang.String, java.lang.String) + */ + public WorkflowPath signal(String pathId, String transition) + { + String engineId = BPMEngineRegistry.getEngineId(pathId); + WorkflowComponent component = getWorkflowComponent(engineId); + return component.signal(pathId, transition); + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.workflow.WorkflowService#getTasksForWorkflowPath(java.lang.String) + */ + public List getTasksForWorkflowPath(String pathId) + { + String engineId = BPMEngineRegistry.getEngineId(pathId); + WorkflowComponent component = getWorkflowComponent(engineId); + return component.getTasksForWorkflowPath(pathId); + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.workflow.WorkflowService#getAssignedTasks(java.lang.String, org.alfresco.service.cmr.workflow.WorkflowTaskState) + */ + public List getAssignedTasks(String authority, WorkflowTaskState state) + { + List tasks = new ArrayList(10); + String[] ids = registry.getTaskComponents(); + for (String id: ids) + { + TaskComponent component = registry.getTaskComponent(id); + tasks.addAll(component.getAssignedTasks(authority, state)); + } + return Collections.unmodifiableList(tasks); + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.workflow.WorkflowService#getPooledTasks(java.lang.String) + */ + public List getPooledTasks(String authority) + { + // TODO: Expand authorities to include associated groups (and parent groups) + List authorities = new ArrayList(); + authorities.add(authority); + + List tasks = new ArrayList(10); + String[] ids = registry.getTaskComponents(); + for (String id: ids) + { + TaskComponent component = registry.getTaskComponent(id); + tasks.addAll(component.getPooledTasks(authorities)); + } + return Collections.unmodifiableList(tasks); + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.workflow.WorkflowService#updateTask(java.lang.String, java.util.Map, java.util.Map, java.util.Map) + */ + public WorkflowTask updateTask(String taskId, Map properties, Map> add, Map> remove) + { + String engineId = BPMEngineRegistry.getEngineId(taskId); + TaskComponent component = getTaskComponent(engineId); + return component.updateTask(taskId, properties, add, remove); + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.workflow.WorkflowService#endTask(java.lang.String, java.lang.String) + */ + public WorkflowTask endTask(String taskId, String transition) + { + String engineId = BPMEngineRegistry.getEngineId(taskId); + TaskComponent component = getTaskComponent(engineId); + return component.endTask(taskId, transition); + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.workflow.WorkflowService#getTaskById(java.lang.String) + */ + public WorkflowTask getTaskById(String taskId) + { + String engineId = BPMEngineRegistry.getEngineId(taskId); + TaskComponent component = getTaskComponent(engineId); + return component.getTaskById(taskId); + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.workflow.WorkflowService#createPackage(java.lang.String, org.alfresco.service.cmr.repository.NodeRef) + */ + public NodeRef createPackage(NodeRef container) + { + return workflowPackageComponent.createPackage(container); + } + + /** + * Gets the Workflow Component registered against the specified BPM Engine Id + * + * @param engineId engine id + */ + private WorkflowComponent getWorkflowComponent(String engineId) + { + WorkflowComponent component = registry.getWorkflowComponent(engineId); + if (component == null) + { + throw new WorkflowException("Workflow Component for engine id '" + engineId + "' is not registered"); + } + return component; + } + + /** + * Gets the Task Component registered against the specified BPM Engine Id + * + * @param engineId engine id + */ + private TaskComponent getTaskComponent(String engineId) + { + TaskComponent component = registry.getTaskComponent(engineId); + if (component == null) + { + throw new WorkflowException("Task Component for engine id '" + engineId + "' is not registered"); + } + return component; + } + +} diff --git a/source/java/org/alfresco/repo/workflow/WorkflowServiceImplTest.java b/source/java/org/alfresco/repo/workflow/WorkflowServiceImplTest.java new file mode 100644 index 0000000000..e61718f534 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/WorkflowServiceImplTest.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.workflow; + +import java.util.List; + +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.workflow.WorkflowDefinition; +import org.alfresco.service.cmr.workflow.WorkflowPath; +import org.alfresco.service.cmr.workflow.WorkflowService; +import org.alfresco.util.BaseSpringTest; + + +/** + * Workflow Service Implementation Tests + * + * @author davidc + */ +public class WorkflowServiceImplTest extends BaseSpringTest +{ + WorkflowService workflowService; + NodeService nodeService; + + //@Override + protected void onSetUpInTransaction() throws Exception + { + workflowService = (WorkflowService)applicationContext.getBean(ServiceRegistry.WORKFLOW_SERVICE.getLocalName()); + nodeService = (NodeService)applicationContext.getBean(ServiceRegistry.NODE_SERVICE.getLocalName()); + } + + public void testGetWorkflowDefinitions() + { + List workflowDefs = workflowService.getDefinitions(); + assertNotNull(workflowDefs); + assertTrue(workflowDefs.size() > 0); + } + + public void testStartWorkflow() + { + List workflowDefs = workflowService.getDefinitions(); + assertNotNull(workflowDefs); + assertTrue(workflowDefs.size() > 0); + WorkflowDefinition workflowDef = workflowDefs.get(0); + WorkflowPath path = workflowService.startWorkflow(workflowDef.id, null); + assertNotNull(path); + assertTrue(path.active); + assertNotNull(path.node); + assertNotNull(path.instance); + assertEquals(workflowDef.id, path.instance.definition.id); + } + + public void testWorkflowPackage() + { + NodeRef nodeRef = workflowService.createPackage(null); + assertNotNull(nodeRef); + assertTrue(nodeService.hasAspect(nodeRef, WorkflowModel.ASPECT_WORKFLOW_PACKAGE)); + } + +} diff --git a/source/java/org/alfresco/repo/workflow/jbpm/AlfrescoJavaScript.java b/source/java/org/alfresco/repo/workflow/jbpm/AlfrescoJavaScript.java new file mode 100644 index 0000000000..b3bab2b2db --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/jbpm/AlfrescoJavaScript.java @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.workflow.jbpm; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.ScriptService; +import org.dom4j.Element; +import org.jbpm.context.def.VariableAccess; +import org.jbpm.context.exe.ContextInstance; +import org.jbpm.graph.exe.ExecutionContext; +import org.jbpm.graph.exe.Token; +import org.jbpm.jpdl.xml.JpdlXmlReader; +import org.springframework.beans.factory.BeanFactory; +import org.xml.sax.InputSource; + +/** + * A jBPM Action Handler for executing Alfresco Script + * + * The configuration of this action is as follows: + * + * + * It's exactly the same as jBPM's own script configuration. + * + * @author davidc + */ +public class AlfrescoJavaScript extends JBPMSpringActionHandler +{ + private static final long serialVersionUID = -2908748080671212745L; + + private static JpdlXmlReader jpdlReader = new JpdlXmlReader((InputSource)null); + private ScriptService scriptService; + private Element script; + + + /* (non-Javadoc) + * @see org.alfresco.repo.workflow.jbpm.JBPMSpringActionHandler#initialiseHandler(org.springframework.beans.factory.BeanFactory) + */ + @Override + protected void initialiseHandler(BeanFactory factory) + { + scriptService = (ScriptService)factory.getBean(ServiceRegistry.SCRIPT_SERVICE.getLocalName()); + } + + + /* (non-Javadoc) + * @see org.jbpm.graph.def.ActionHandler#execute(org.jbpm.graph.exe.ExecutionContext) + */ + @SuppressWarnings("unchecked") + public void execute(ExecutionContext executionContext) throws Exception + { + // extract action configuration + String expression = null; + List variableAccesses = null; + + // is the script specified as text only, or as explicit expression, variable elements + boolean isTextOnly = true; + Iterator iter = script.elementIterator(); + while (iter.hasNext()) + { + Element element = iter.next(); + if (element.getNodeType() == Element.ELEMENT_NODE) + { + isTextOnly = false; + } + } + + // extract script and variables + if (isTextOnly) + { + expression = script.getTextTrim(); + } + else + { + variableAccesses = jpdlReader.readVariableAccesses(script); + expression = script.element("expression").getTextTrim(); + } + + // construct script arguments and execute + Map inputMap = createInputMap(executionContext, variableAccesses); + Object result = scriptService.executeScriptString(expression, inputMap); + + // map script return variable to process context + VariableAccess returnVariable = getWritableVariable(variableAccesses); + if (returnVariable != null) + { + ContextInstance contextInstance = executionContext.getContextInstance(); + Token token = executionContext.getToken(); + contextInstance.setVariable(returnVariable.getVariableName(), result, token); + } + } + + + /** + * Construct map of arguments to pass to script + * + * Based on the elements of the action configuration. + * + * @param executionContext the execution context + * @param variableAccesses the variable configuration + * @return the map of script arguments + */ + @SuppressWarnings("unchecked") + public Map createInputMap(ExecutionContext executionContext, List variableAccesses) + { + Map inputMap = new HashMap(); + + // initialise process variables + Token token = executionContext.getToken(); + inputMap.put("executionContext", executionContext); + inputMap.put("token", token); + if (executionContext.getNode() != null) + { + inputMap.put("node", executionContext.getNode()); + } + if (executionContext.getTask() != null) + { + inputMap.put("task", executionContext.getTask()); + } + if (executionContext.getTaskInstance() != null) + { + inputMap.put("taskInstance", executionContext.getTaskInstance()); + } + + // if no readable variableInstances are specified, + ContextInstance contextInstance = executionContext.getContextInstance(); + if (!hasReadableVariable(variableAccesses)) + { + // copy all the variableInstances of the context into the interpreter + Map variables = contextInstance.getVariables(token); + if (variables != null) + { + for (Map.Entry entry : variables.entrySet()) + { + String variableName = (String) entry.getKey(); + Object variableValue = entry.getValue(); + inputMap.put(variableName, variableValue); + } + } + } + else + { + // copy the specified variableInstances into the interpreterz + for (VariableAccess variableAccess : variableAccesses) + { + if (variableAccess.isReadable()) + { + String variableName = variableAccess.getVariableName(); + String mappedName = variableAccess.getMappedName(); + Object variableValue = contextInstance.getVariable(variableName, token); + inputMap.put(mappedName, variableValue); + } + } + } + + return inputMap; + } + + + /** + * Determine if there are variables to read from the process context + * + * @param variableAccesses the variables configuration + * @return true => there are variables to read + */ + private boolean hasReadableVariable(List variableAccesses) + { + if (variableAccesses != null) + { + for (VariableAccess variableAccess : variableAccesses) + { + if (variableAccess.isReadable()) + { + return true; + } + } + } + return false; + } + + + /** + * Determine if there is a variable to write back to the process context + * + * @param variableAccesses the variables configuration + * @return true => there is a variable to write + */ + private VariableAccess getWritableVariable(List variableAccesses) + { + if (variableAccesses != null) + { + for (VariableAccess variableAccess : variableAccesses) + { + if (variableAccess.isWritable()) + { + return variableAccess; + } + } + } + return null; + } + +} diff --git a/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java b/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java new file mode 100644 index 0000000000..b392d69d64 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java @@ -0,0 +1,1636 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.workflow.jbpm; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import java.util.zip.ZipInputStream; + +import org.alfresco.i18n.I18NUtil; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.workflow.BPMEngine; +import org.alfresco.repo.workflow.TaskComponent; +import org.alfresco.repo.workflow.WorkflowComponent; +import org.alfresco.repo.workflow.WorkflowModel; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.dictionary.AspectDefinition; +import org.alfresco.service.cmr.dictionary.AssociationDefinition; +import org.alfresco.service.cmr.dictionary.ClassDefinition; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.service.cmr.dictionary.TypeDefinition; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.cmr.workflow.WorkflowDefinition; +import org.alfresco.service.cmr.workflow.WorkflowDeployment; +import org.alfresco.service.cmr.workflow.WorkflowException; +import org.alfresco.service.cmr.workflow.WorkflowInstance; +import org.alfresco.service.cmr.workflow.WorkflowNode; +import org.alfresco.service.cmr.workflow.WorkflowPath; +import org.alfresco.service.cmr.workflow.WorkflowTask; +import org.alfresco.service.cmr.workflow.WorkflowTaskDefinition; +import org.alfresco.service.cmr.workflow.WorkflowTaskState; +import org.alfresco.service.cmr.workflow.WorkflowTransition; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.hibernate.Query; +import org.hibernate.Session; +import org.hibernate.proxy.HibernateProxy; +import org.jbpm.JbpmContext; +import org.jbpm.JbpmException; +import org.jbpm.context.exe.ContextInstance; +import org.jbpm.db.GraphSession; +import org.jbpm.db.TaskMgmtSession; +import org.jbpm.graph.def.Node; +import org.jbpm.graph.def.ProcessDefinition; +import org.jbpm.graph.def.Transition; +import org.jbpm.graph.exe.ProcessInstance; +import org.jbpm.graph.exe.Token; +import org.jbpm.jpdl.par.ProcessArchive; +import org.jbpm.jpdl.xml.JpdlXmlReader; +import org.jbpm.jpdl.xml.Problem; +import org.jbpm.taskmgmt.def.Task; +import org.jbpm.taskmgmt.exe.TaskInstance; +import org.springframework.util.StringUtils; +import org.springmodules.workflow.jbpm31.JbpmCallback; +import org.springmodules.workflow.jbpm31.JbpmTemplate; +import org.xml.sax.InputSource; + + +/** + * JBoss JBPM based implementation of: + * + * Workflow Definition Component + * Workflow Component + * Task Component + * + * @author davidc + */ +public class JBPMEngine extends BPMEngine + implements WorkflowComponent, TaskComponent +{ + // Implementation dependencies + protected DictionaryService dictionaryService; + protected NamespaceService namespaceService; + protected NodeService nodeService; + protected ServiceRegistry serviceRegistry; + protected PersonService personService; + protected JbpmTemplate jbpmTemplate; + + // Note: jBPM query which is not provided out-of-the-box + // TODO: Check jBPM 3.2 and get this implemented in jBPM + private final static String COMPLETED_TASKS_QUERY = + "select ti " + + "from org.jbpm.taskmgmt.exe.TaskInstance as ti " + + "where ti.actorId = :actorId " + + "and ti.isOpen = false " + + "and ti.end is not null"; + + // Workflow Path Seperators + private final static String WORKFLOW_PATH_SEPERATOR = "-"; + private final static String WORKFLOW_TOKEN_SEPERATOR = "@"; + + // I18N labels + private final static String TITLE_LABEL = "title"; + private final static String DESC_LABEL = "description"; + private final static String DEFAULT_TRANSITION_LABEL = "bpm_businessprocessmodel.transition"; + + + /** + * Sets the JBPM Template used for accessing JBoss JBPM in the correct context + * + * @param jbpmTemplate + */ + public void setJBPMTemplate(JbpmTemplate jbpmTemplate) + { + this.jbpmTemplate = jbpmTemplate; + } + + /** + * Sets the Dictionary Service + * + * @param dictionaryService + */ + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + /** + * Sets the Namespace Service + * + * @param namespaceService + */ + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + /** + * Sets the Node Service + * + * @param nodeService + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * Sets the Person Service + * + * @param personService + */ + public void setPersonService(PersonService personService) + { + this.personService = personService; + } + + /** + * Sets the Service Registry + * + * @param serviceRegistry + */ + public void setServiceRegistry(ServiceRegistry serviceRegistry) + { + this.serviceRegistry = serviceRegistry; + } + + + // + // Workflow Definition... + // + + /* (non-Javadoc) + * @see org.alfresco.repo.workflow.WorkflowDefinitionComponent#deployDefinition(java.io.InputStream) + */ + public WorkflowDeployment deployDefinition(final InputStream workflowDefinition, final String mimetype) + { + try + { + return (WorkflowDeployment)jbpmTemplate.execute(new JbpmCallback() + { + public Object doInJbpm(JbpmContext context) + { + // construct process definition + CompiledProcessDefinition compiledDef = compileProcessDefinition(workflowDefinition, mimetype); + + // deploy the parsed definition + context.deployProcessDefinition(compiledDef.def); + + // return deployed definition + WorkflowDeployment workflowDeployment = createWorkflowDeployment(compiledDef); + return workflowDeployment; + } + }); + } + catch(JbpmException e) + { + throw new WorkflowException("Failed to deploy workflow definition", e); + } + } + + + /* (non-Javadoc) + * @see org.alfresco.repo.workflow.WorkflowDefinitionComponent#isDefinitionDeployed(java.io.InputStream, java.lang.String) + */ + public boolean isDefinitionDeployed(final InputStream workflowDefinition, final String mimetype) + { + try + { + return (Boolean) jbpmTemplate.execute(new JbpmCallback() + { + public Boolean doInJbpm(JbpmContext context) + { + // create process definition from input stream + CompiledProcessDefinition processDefinition = compileProcessDefinition(workflowDefinition, mimetype); + + // retrieve process definition from Alfresco Repository + GraphSession graphSession = context.getGraphSession(); + ProcessDefinition existingDefinition = graphSession.findLatestProcessDefinition(processDefinition.def.getName()); + return (existingDefinition == null) ? false : true; + } + }); + } + catch(JbpmException e) + { + throw new WorkflowException("Failed to determine if workflow definition is already deployed", e); + } + } + + /* (non-Javadoc) + * @see org.alfresco.repo.workflow.WorkflowDefinitionComponent#undeployDefinition(java.lang.String) + */ + public void undeployDefinition(final String workflowDefinitionId) + { + try + { + jbpmTemplate.execute(new JbpmCallback() + { + public Object doInJbpm(JbpmContext context) + { + // retrieve process definition + GraphSession graphSession = context.getGraphSession(); + ProcessDefinition processDefinition = graphSession.loadProcessDefinition(getJbpmId(workflowDefinitionId)); + // NOTE: if not found, should throw an exception + + // undeploy + // NOTE: jBPM deletes all "in-flight" processes too + // TODO: Determine if there's a safer undeploy we can expose via the WorkflowService contract + graphSession.deleteProcessDefinition(processDefinition); + + // we're done + return null; + } + }); + } + catch(JbpmException e) + { + throw new WorkflowException("Failed to deploy workflow definition", e); + } + } + + /* (non-Javadoc) + * @see org.alfresco.repo.workflow.WorkflowDefinitionComponent#getDefinitions() + */ + @SuppressWarnings("unchecked") + public List getDefinitions() + { + try + { + return (List)jbpmTemplate.execute(new JbpmCallback() + { + public Object doInJbpm(JbpmContext context) + { + GraphSession graphSession = context.getGraphSession(); + List processDefs = (List)graphSession.findLatestProcessDefinitions(); + List workflowDefs = new ArrayList(processDefs.size()); + for (ProcessDefinition processDef : processDefs) + { + WorkflowDefinition workflowDef = createWorkflowDefinition(processDef); + workflowDefs.add(workflowDef); + } + return workflowDefs; + } + }); + } + catch(JbpmException e) + { + throw new WorkflowException("Failed to retrieve workflow definitions", e); + } + } + + /* (non-Javadoc) + * @see org.alfresco.repo.workflow.WorkflowDefinitionComponent#getDefinitionById(java.lang.String) + */ + public WorkflowDefinition getDefinitionById(String workflowDefinitionId) + { + // TODO + throw new UnsupportedOperationException(); + } + + /* (non-Javadoc) + * @see org.alfresco.repo.workflow.WorkflowComponent#getDefinitionByName(java.lang.String) + */ + public WorkflowDefinition getDefinitionByName(final String workflowName) + { + try + { + return (WorkflowDefinition)jbpmTemplate.execute(new JbpmCallback() + { + @SuppressWarnings("synthetic-access") + public Object doInJbpm(JbpmContext context) + { + GraphSession graphSession = context.getGraphSession(); + ProcessDefinition processDef = graphSession.findLatestProcessDefinition(createLocalId(workflowName)); + return createWorkflowDefinition(processDef); + } + }); + } + catch(JbpmException e) + { + throw new WorkflowException("Failed to retrieve workflow definition '" + workflowName + "'", e); + } + } + + + // + // Workflow Instance Management... + // + + + /* (non-Javadoc) + * @see org.alfresco.repo.workflow.WorkflowComponent#startWorkflow(java.lang.String, java.util.Map) + */ + @SuppressWarnings("unchecked") + public WorkflowPath startWorkflow(final String workflowDefinitionId, final Map parameters) + { + try + { + return (WorkflowPath) jbpmTemplate.execute(new JbpmCallback() + { + @SuppressWarnings("synthetic-access") + public Object doInJbpm(JbpmContext context) + { + // initialise jBPM actor (for any processes that wish to record the initiator) + String currentUserName = AuthenticationUtil.getCurrentUserName(); + context.setActorId(currentUserName); + + // construct a new process + GraphSession graphSession = context.getGraphSession(); + ProcessDefinition processDefinition = graphSession.loadProcessDefinition(getJbpmId(workflowDefinitionId)); + ProcessInstance processInstance = new ProcessInstance(processDefinition); + + // assign initial process context + ContextInstance processContext = processInstance.getContextInstance(); + NodeRef initiatorPerson = mapNameToAuthority(currentUserName); + if (initiatorPerson != null) + { + processContext.setVariable("initiator", new JBPMNode(initiatorPerson, serviceRegistry)); + } + + // create the start task if one exists + Token token = processInstance.getRootToken(); + Task startTask = processInstance.getTaskMgmtInstance().getTaskMgmtDefinition().getStartTask(); + if (startTask != null) + { + TaskInstance taskInstance = processInstance.getTaskMgmtInstance().createStartTaskInstance(); + setTaskProperties(taskInstance, parameters); + token = taskInstance.getToken(); + } + + // Save the process instance along with the task instance + context.save(processInstance); + return createWorkflowPath(token); + } + }); + } + catch(JbpmException e) + { + throw new WorkflowException("Failed to start workflow " + workflowDefinitionId, e); + } + } + + /* (non-Javadoc) + * @see org.alfresco.repo.workflow.WorkflowComponent#getActiveWorkflows(java.lang.String) + */ + @SuppressWarnings("unchecked") + public List getActiveWorkflows(final String workflowDefinitionId) + { + try + { + return (List) jbpmTemplate.execute(new JbpmCallback() + { + public Object doInJbpm(JbpmContext context) + { + GraphSession graphSession = context.getGraphSession(); + List processInstances = graphSession.findProcessInstances(getJbpmId(workflowDefinitionId)); + List workflowInstances = new ArrayList(processInstances.size()); + for (ProcessInstance processInstance : processInstances) + { + if (!processInstance.hasEnded()) + { + WorkflowInstance workflowInstance = createWorkflowInstance(processInstance); + workflowInstances.add(workflowInstance); + } + } + return workflowInstances; + } + }); + } + catch(JbpmException e) + { + throw new WorkflowException("Failed to retrieve workflow instances for definition '" + workflowDefinitionId + "'", e); + } + } + + /* (non-Javadoc) + * @see org.alfresco.repo.workflow.WorkflowComponent#getWorkflowPaths(java.lang.String) + */ + @SuppressWarnings("unchecked") + public List getWorkflowPaths(final String workflowId) + { + try + { + return (List) jbpmTemplate.execute(new JbpmCallback() + { + public Object doInJbpm(JbpmContext context) + { + // retrieve process instance + GraphSession graphSession = context.getGraphSession(); + ProcessInstance processInstance = graphSession.loadProcessInstance(getJbpmId(workflowId)); + + // convert jBPM tokens to workflow posisitons + List tokens = processInstance.findAllTokens(); + List paths = new ArrayList(tokens.size()); + for (Token token : tokens) + { + if (!token.hasEnded()) + { + WorkflowPath path = createWorkflowPath(token); + paths.add(path); + } + } + + return paths; + } + }); + } + catch(JbpmException e) + { + throw new WorkflowException("Failed to retrieve workflow paths for workflow instance '" + workflowId + "'", e); + } + } + + /* (non-Javadoc) + * @see org.alfresco.repo.workflow.WorkflowComponent#cancelWorkflow(java.lang.String) + */ + public WorkflowInstance cancelWorkflow(final String workflowId) + { + try + { + return (WorkflowInstance) jbpmTemplate.execute(new JbpmCallback() + { + public Object doInJbpm(JbpmContext context) + { + // retrieve and cancel process instance + GraphSession graphSession = context.getGraphSession(); + ProcessInstance processInstance = graphSession.loadProcessInstance(getJbpmId(workflowId)); + processInstance.end(); + + // save the process instance along with the task instance + context.save(processInstance); + return createWorkflowInstance(processInstance); + } + }); + } + catch(JbpmException e) + { + throw new WorkflowException("Failed to cancel workflow instance '" + workflowId + "'", e); + } + } + + /* (non-Javadoc) + * @see org.alfresco.repo.workflow.WorkflowComponent#signal(java.lang.String, java.lang.String) + */ + public WorkflowPath signal(final String pathId, final String transition) + { + try + { + return (WorkflowPath) jbpmTemplate.execute(new JbpmCallback() + { + public Object doInJbpm(JbpmContext context) + { + // retrieve jBPM token for workflow position + GraphSession graphSession = context.getGraphSession(); + Token token = getWorkflowToken(graphSession, pathId); + + // signal the transition + if (transition == null) + { + token.signal(); + } + else + { + Node node = token.getNode(); + if (!node.hasLeavingTransition(transition)) + { + throw new WorkflowException("Transition '" + transition + "' is invalid for Workflow path '" + pathId + "'"); + } + token.signal(transition); + } + + // save + ProcessInstance processInstance = token.getProcessInstance(); + context.save(processInstance); + + // return new workflow path + return createWorkflowPath(token); + } + }); + } + catch(JbpmException e) + { + throw new WorkflowException("Failed to signal transition '" + transition + "' from workflow path '" + pathId + "'", e); + } + } + + /* (non-Javadoc) + * @see org.alfresco.repo.workflow.WorkflowComponent#getTasksForWorkflowPath(java.lang.String) + */ + @SuppressWarnings("unchecked") + public List getTasksForWorkflowPath(final String pathId) + { + try + { + return (List) jbpmTemplate.execute(new JbpmCallback() + { + public List doInJbpm(JbpmContext context) + { + // retrieve tasks at specified workflow path + GraphSession graphSession = context.getGraphSession(); + Token token = getWorkflowToken(graphSession, pathId); + TaskMgmtSession taskSession = context.getTaskMgmtSession(); + List tasks = taskSession.findTaskInstancesByToken(token.getId()); + List workflowTasks = new ArrayList(tasks.size()); + for (TaskInstance task : tasks) + { + WorkflowTask workflowTask = createWorkflowTask(task); + workflowTasks.add(workflowTask); + } + return workflowTasks; + } + }); + } + catch(JbpmException e) + { + throw new WorkflowException("Failed to retrieve tasks assigned at Workflow path '" + pathId + "'", e); + } + } + + + // + // Task Management ... + // + + + /* (non-Javadoc) + * @see org.alfresco.repo.workflow.TaskComponent#getAssignedTasks(java.lang.String, org.alfresco.service.cmr.workflow.WorkflowTaskState) + */ + @SuppressWarnings("unchecked") + public List getAssignedTasks(final String authority, final WorkflowTaskState state) + { + try + { + return (List) jbpmTemplate.execute(new JbpmCallback() + { + public List doInJbpm(JbpmContext context) + { + // retrieve tasks assigned to authority + List tasks; + if (state.equals(WorkflowTaskState.IN_PROGRESS)) + { + TaskMgmtSession taskSession = context.getTaskMgmtSession(); + tasks = taskSession.findTaskInstances(authority); + } + else + { + // Note: This method is not implemented by jBPM + tasks = findCompletedTaskInstances(context, authority); + } + + // convert tasks to appropriate service response format + List workflowTasks = new ArrayList(tasks.size()); + for (TaskInstance task : tasks) + { + WorkflowTask workflowTask = createWorkflowTask(task); + workflowTasks.add(workflowTask); + } + return workflowTasks; + } + + /** + * Gets the completed task list for the specified actor + * + * TODO: This method provides a query that's not in JBPM! Look to have JBPM implement this. + * + * @param jbpmContext the jbpm context + * @param actorId the actor to retrieve tasks for + * @return the tasks + */ + private List findCompletedTaskInstances(JbpmContext jbpmContext, String actorId) + { + List result = null; + try + { + Session session = jbpmContext.getSession(); + Query query = session.createQuery(COMPLETED_TASKS_QUERY); + query.setString("actorId", actorId); + result = query.list(); + } + catch (Exception e) + { + throw new JbpmException("Couldn't get completed task instances list for actor '" + actorId + "'", e); + } + return result; + } + }); + } + catch(JbpmException e) + { + throw new WorkflowException("Failed to retrieve tasks assigned to authority '" + authority + "' in state '" + state + "'", e); + } + } + + /* (non-Javadoc) + * @see org.alfresco.repo.workflow.TaskComponent#getPooledTasks(java.util.List) + */ + @SuppressWarnings("unchecked") + public List getPooledTasks(final List authorities) + { + try + { + return (List) jbpmTemplate.execute(new JbpmCallback() + { + public List doInJbpm(JbpmContext context) + { + // retrieve pooled tasks for specified authorities + TaskMgmtSession taskSession = context.getTaskMgmtSession(); + List tasks = taskSession.findPooledTaskInstances(authorities); + List workflowTasks = new ArrayList(tasks.size()); + for (TaskInstance task : tasks) + { + WorkflowTask workflowTask = createWorkflowTask(task); + workflowTasks.add(workflowTask); + } + return workflowTasks; + } + }); + } + catch(JbpmException e) + { + throw new WorkflowException("Failed to retrieve pooled tasks for authorities '" + authorities + "'", e); + } + } + + /* (non-Javadoc) + * @see org.alfresco.repo.workflow.TaskComponent#updateTask(java.lang.String, java.util.Map, java.util.Map, java.util.Map) + */ + public WorkflowTask updateTask(final String taskId, final Map properties, final Map> add, final Map> remove) + { + try + { + return (WorkflowTask) jbpmTemplate.execute(new JbpmCallback() + { + public Object doInJbpm(JbpmContext context) + { + // retrieve task + TaskMgmtSession taskSession = context.getTaskMgmtSession(); + TaskInstance taskInstance = taskSession.loadTaskInstance(getJbpmId(taskId)); + + // create properties to set on task instance + Map newProperties = properties; + if (newProperties == null && (add != null || remove != null)) + { + newProperties = new HashMap(10); + } + + if (add != null || remove != null) + { + Map existingProperties = getTaskProperties(taskInstance); + + if (add != null) + { + // add new associations + for (Entry> toAdd : add.entrySet()) + { + // retrieve existing list of noderefs for association + List existingAdd = (List)newProperties.get(toAdd.getKey()); + if (existingAdd == null) + { + existingAdd = (List)existingProperties.get(toAdd.getKey()); + } + + // make the additions + if (existingAdd == null) + { + newProperties.put(toAdd.getKey(), (Serializable)toAdd.getValue()); + } + else + { + for (NodeRef nodeRef : (List)toAdd.getValue()) + { + if (!(existingAdd.contains(nodeRef))) + { + existingAdd.add(nodeRef); + } + } + } + } + } + + if (remove != null) + { + // add new associations + for (Entry> toRemove: remove.entrySet()) + { + // retrieve existing list of noderefs for association + List existingRemove = (List)newProperties.get(toRemove.getKey()); + if (existingRemove == null) + { + existingRemove = (List)existingProperties.get(toRemove.getKey()); + } + + // make the subtractions + if (existingRemove != null) + { + for (NodeRef nodeRef : (List)toRemove.getValue()) + { + existingRemove.remove(nodeRef); + } + } + } + } + } + + // update the task + if (newProperties != null) + { + setTaskProperties(taskInstance, newProperties); + + // save + ProcessInstance processInstance = taskInstance.getToken().getProcessInstance(); + context.save(processInstance); + } + + // note: the ending of a task may not have signalled (i.e. more than one task exists at + // this node) + return createWorkflowTask(taskInstance); + } + }); + } + catch(JbpmException e) + { + throw new WorkflowException("Failed to update workflow task '" + taskId + "'", e); + } + } + + /* (non-Javadoc) + * @see org.alfresco.repo.workflow.TaskComponent#startTask(java.lang.String) + */ + public WorkflowTask startTask(String taskId) + { + // TODO: + throw new UnsupportedOperationException(); + } + + /* (non-Javadoc) + * @see org.alfresco.repo.workflow.TaskComponent#suspendTask(java.lang.String) + */ + public WorkflowTask suspendTask(String taskId) + { + // TODO: + throw new UnsupportedOperationException(); + } + + /* (non-Javadoc) + * @see org.alfresco.repo.workflow.TaskComponent#endTask(java.lang.String, java.lang.String) + */ + public WorkflowTask endTask(final String taskId, final String transition) + { + try + { + return (WorkflowTask) jbpmTemplate.execute(new JbpmCallback() + { + public Object doInJbpm(JbpmContext context) + { + // retrieve task + TaskMgmtSession taskSession = context.getTaskMgmtSession(); + TaskInstance taskInstance = taskSession.loadTaskInstance(getJbpmId(taskId)); + + // signal the transition on the task + if (transition == null) + { + taskInstance.end(); + } + else + { + Node node = taskInstance.getToken().getNode(); + if (node.getLeavingTransition(transition) == null) + { + throw new WorkflowException("Transition '" + transition + "' is invalid for Workflow task '" + taskId + "'"); + } + taskInstance.end(transition); + } + + // save + ProcessInstance processInstance = taskInstance.getToken().getProcessInstance(); + context.save(processInstance); + + // note: the ending of a task may not have signalled (i.e. more than one task exists at + // this node) + return createWorkflowTask(taskInstance); + } + }); + } + catch(JbpmException e) + { + throw new WorkflowException("Failed to signal transition '" + transition + "' from workflow task '" + taskId + "'", e); + } + } + + /* (non-Javadoc) + * @see org.alfresco.repo.workflow.TaskComponent#getTaskById(java.lang.String) + */ + public WorkflowTask getTaskById(final String taskId) + { + try + { + return (WorkflowTask) jbpmTemplate.execute(new JbpmCallback() + { + public Object doInJbpm(JbpmContext context) + { + // retrieve task + TaskMgmtSession taskSession = context.getTaskMgmtSession(); + TaskInstance taskInstance = taskSession.loadTaskInstance(getJbpmId(taskId)); + return createWorkflowTask(taskInstance); + } + }); + } + catch(JbpmException e) + { + throw new WorkflowException("Failed to retrieve task '" + taskId + "'", e); + } + } + + + // + // Helpers... + // + + + /** + * Process Definition with accompanying problems + */ + private static class CompiledProcessDefinition + { + public CompiledProcessDefinition(ProcessDefinition def, List problems) + { + this.def = def; + this.problems = new String[problems.size()]; + int i = 0; + for (Problem problem : problems) + { + this.problems[i++] = problem.toString(); + } + } + + protected ProcessDefinition def; + protected String[] problems; + } + + /** + * JpdlXmlReader with access to problems encountered during compile. + * + * @author davidc + */ + private static class JBPMEngineJpdlXmlReader extends JpdlXmlReader + { + private static final long serialVersionUID = -753730152120696221L; + + public JBPMEngineJpdlXmlReader(InputStream inputStream) + { + super(new InputSource(inputStream)); + } + + /** + * Gets the problems + * + * @return problems + */ + public List getProblems() + { + return problems; + } + } + + /** + * Construct a Process Definition from the provided Process Definition stream + * + * @param workflowDefinition stream to create process definition from + * @param mimetype mimetype of stream + * @return process definition + */ + @SuppressWarnings("unchecked") + protected CompiledProcessDefinition compileProcessDefinition(InputStream definitionStream, String mimetype) + { + String actualMimetype = (mimetype == null) ? MimetypeMap.MIMETYPE_ZIP : mimetype; + CompiledProcessDefinition compiledDef = null; + + // parse process definition from jBPM process archive file + + if (actualMimetype.equals(MimetypeMap.MIMETYPE_ZIP)) + { + ZipInputStream zipInputStream = null; + try + { + zipInputStream = new ZipInputStream(definitionStream); + ProcessArchive reader = new ProcessArchive(zipInputStream); + ProcessDefinition def = reader.parseProcessDefinition(); + compiledDef = new CompiledProcessDefinition(def, reader.getProblems()); + } + catch(Exception e) + { + throw new JbpmException("Failed to parse process definition from jBPM zip archive stream", e); + } + finally + { + if (zipInputStream != null) + { + try { zipInputStream.close(); } catch(IOException e) {}; + } + } + } + + // parse process definition from jBPM xml file + + else if (actualMimetype.equals(MimetypeMap.MIMETYPE_XML)) + { + try + { + JBPMEngineJpdlXmlReader jpdlReader = new JBPMEngineJpdlXmlReader(definitionStream); + ProcessDefinition def = jpdlReader.readProcessDefinition(); + compiledDef = new CompiledProcessDefinition(def, jpdlReader.getProblems()); + } + catch(Exception e) + { + throw new JbpmException("Failed to parse process definition from jBPM xml stream", e); + } + } + + return compiledDef; + } + + /** + * Gets the Task definition of the specified Task + * + * @param task the task + * @return the task definition + */ + private TypeDefinition getTaskDefinition(Task task) + { + // TODO: Extend jBPM task instance to include dictionary definition qname? + QName typeName = QName.createQName(task.getName(), namespaceService); + TypeDefinition typeDef = dictionaryService.getType(typeName); + if (typeDef == null) + { + typeDef = dictionaryService.getType(WorkflowModel.TYPE_WORKFLOW_TASK); + if (typeDef == null) + { + throw new WorkflowException("Failed to find type definition '" + WorkflowModel.TYPE_WORKFLOW_TASK + "'"); + } + } + return typeDef; + } + + /** + * Convert the specified Type definition to an anonymous Type definition. + * + * This collapses all mandatory aspects into a single Type definition. + * + * @param typeDef the type definition + * @return the anonymous type definition + */ + private TypeDefinition getAnonymousTaskDefinition(TypeDefinition typeDef) + { + List aspects = typeDef.getDefaultAspects(); + List aspectNames = new ArrayList(aspects.size()); + getMandatoryAspects(typeDef, aspectNames); + return dictionaryService.getAnonymousType(typeDef.getName(), aspectNames); + } + + /** + * Gets a flattened list of all mandatory aspects for a given class + * + * @param classDef the class + * @param aspects a list to hold the mandatory aspects + */ + private void getMandatoryAspects(ClassDefinition classDef, List aspects) + { + for (AspectDefinition aspect : classDef.getDefaultAspects()) + { + QName aspectName = aspect.getName(); + if (!aspects.contains(aspectName)) + { + aspects.add(aspect.getName()); + getMandatoryAspects(aspect, aspects); + } + } + } + + /** + * Get JBoss JBPM Id from Engine Global Id + * + * @param id global id + * @return JBoss JBPM Id + */ + protected long getJbpmId(String id) + { + try + { + String theLong = createLocalId(id); + return new Long(theLong); + } + catch(NumberFormatException e) + { + throw new WorkflowException("Format of id '" + id + "' is invalid", e); + } + } + + /** + * Get the JBoss JBPM Token for the Workflow Path + * + * @param session JBoss JBPM Graph Session + * @param pathId workflow path id + * @return JBoss JBPM Token + */ + protected Token getWorkflowToken(GraphSession session, String pathId) + { + // extract process id and token path within process + String[] path = pathId.split(WORKFLOW_PATH_SEPERATOR); + if (path.length != 2) + { + throw new WorkflowException("Invalid workflow path '" + pathId + "'"); + } + + // retrieve jBPM token for workflow position + ProcessInstance processInstance = session.loadProcessInstance(getJbpmId(path[0])); + String tokenId = path[1].replace(WORKFLOW_TOKEN_SEPERATOR, "/"); + Token token = processInstance.findToken(tokenId); + if (token == null) + { + throw new WorkflowException("Workflow path '" + pathId + "' does not exist"); + } + + return token; + } + + /** + * Sets Properties of Task + * + * @param instance task instance + * @param properties properties to set + */ + @SuppressWarnings("unchecked") + protected Map getTaskProperties(TaskInstance instance) + { + // map arbitrary task variables + Map properties = new HashMap(10); + Map vars = instance.getVariablesLocally(); + for (Entry entry : vars.entrySet()) + { + String key = entry.getKey(); + Object value = entry.getValue(); + + // + // perform data conversions + // + + // Convert Nodes to NodeRefs + if (value instanceof JBPMNode) + { + value = ((JBPMNode)value).getNodeRef(); + } + else if (value instanceof JBPMNodeList) + { + JBPMNodeList nodes = (JBPMNodeList)value; + List nodeRefs = new ArrayList(nodes.size()); + for (JBPMNode node : nodes) + { + nodeRefs.add(node.getNodeRef()); + } + value = (Serializable)nodeRefs; + } + + // place task variable in map to return + QName qname = QName.createQName(key, this.namespaceService); + properties.put(qname, (Serializable)value); + } + + // map jBPM task instance fields to properties + properties.put(WorkflowModel.PROP_TASK_ID, instance.getId()); + properties.put(WorkflowModel.PROP_START_DATE, instance.getStart()); + properties.put(WorkflowModel.PROP_DUE_DATE, instance.getDueDate()); + properties.put(WorkflowModel.PROP_COMPLETION_DATE, instance.getEnd()); + properties.put(WorkflowModel.PROP_PRIORITY, instance.getPriority()); + properties.put(ContentModel.PROP_CREATED, instance.getCreate()); + properties.put(ContentModel.PROP_OWNER, instance.getActorId()); + + // map jBPM task instance collections to associations + Set pooledActors = instance.getPooledActors(); + if (pooledActors != null) + { + List pooledNodeRefs = new ArrayList(pooledActors.size()); + for (String pooledActor : (Set)pooledActors) + { + NodeRef pooledNodeRef = mapNameToAuthority(pooledActor); + if (pooledNodeRef != null) + { + pooledNodeRefs.add(pooledNodeRef); + } + } + properties.put(WorkflowModel.ASSOC_POOLED_ACTORS, (Serializable)pooledNodeRefs); + } + + return properties; + } + + /** + * Sets Properties of Task + * + * @param instance task instance + * @param properties properties to set + */ + protected void setTaskProperties(TaskInstance instance, Map properties) + { + if (properties == null) + { + return; + } + + // establish task definition + TypeDefinition taskDef = getAnonymousTaskDefinition(getTaskDefinition(instance.getTask())); + Map taskProperties = taskDef.getProperties(); + Map taskAssocs = taskDef.getAssociations(); + + // map each parameter to task + for (Entry entry : properties.entrySet()) + { + QName key = entry.getKey(); + Serializable value = entry.getValue(); + + // determine if writing property + // NOTE: some properties map to fields on jBPM task instance whilst + // others are set in the general variable bag on the task + PropertyDefinition propDef = taskProperties.get(key); + if (propDef != null) + { + if (propDef.isProtected()) + { + // NOTE: only write non-protected properties + continue; + } + + // convert property value + if (value instanceof Collection) + { + value = (Serializable)DefaultTypeConverter.INSTANCE.convert(propDef.getDataType(), (Collection)value); + } + else + { + value = (Serializable)DefaultTypeConverter.INSTANCE.convert(propDef.getDataType(), value); + } + + // convert NodeRefs to JBPMNodes + DataTypeDefinition dataTypeDef = propDef.getDataType(); + if (dataTypeDef.getName().equals(DataTypeDefinition.NODE_REF)) + { + value = convertNodeRefs(propDef.isMultiValued(), value); + } + + // map property to specific jBPM task instance field + if (key.equals(WorkflowModel.PROP_DUE_DATE)) + { + if (value != null && !(value instanceof Date)) + { + throw new WorkflowException("Task due date '" + value + "' is invalid"); + } + instance.setDueDate((Date)value); + continue; + } + else if (key.equals(WorkflowModel.PROP_PRIORITY)) + { + if (!(value instanceof Integer)) + { + throw new WorkflowException("Task priority '" + value + "' is invalid"); + } + instance.setPriority((Integer)value); + continue; + } + else if (key.equals(ContentModel.PROP_OWNER)) + { + if (value != null && !(value instanceof String)) + { + throw new WorkflowException("Task owner '" + value + "' is invalid"); + } + instance.setActorId((String)value); + continue; + } + } + else + { + // determine if writing association + AssociationDefinition assocDef = taskAssocs.get(key); + if (assocDef != null) + { + // convert association to JBPMNodes + value = convertNodeRefs(assocDef.isTargetMany(), value); + + // map association to specific jBPM task instance field + if (key.equals(WorkflowModel.ASSOC_POOLED_ACTORS)) + { + String[] pooledActors = null; + if (value instanceof JBPMNodeList[]) + { + JBPMNodeList actors = (JBPMNodeList)value; + pooledActors = new String[actors.size()]; + int i = 0; + for (JBPMNode actor : actors) + { + pooledActors[i++] = actor.getName(); + } + } + else if (value instanceof JBPMNode) + { + pooledActors = new String[] {((JBPMNode)value).getName()}; + } + else + { + throw new WorkflowException("Pooled actors value '" + value + "' is invalid"); + } + instance.setPooledActors(pooledActors); + continue; + } + } + + // untyped value, perform minimal conversion + else + { + if (value instanceof NodeRef) + { + value = new JBPMNode((NodeRef)value, serviceRegistry); + } + } + } + + // no specific mapping to jBPM task has been established, so place into + // the generic task variable bag + String name = key.toPrefixString(this.namespaceService); + instance.setVariableLocally(name, value); + } + } + + /** + * Sets Default Properties of Task + * + * @param instance task instance + */ + protected void setDefaultTaskProperties(TaskInstance instance) + { + Map existingValues = null; + Map defaultValues = new HashMap(); + + // construct an anonymous type that flattens all mandatory aspects + ClassDefinition classDef = getAnonymousTaskDefinition(getTaskDefinition(instance.getTask())); + Map propertyDefs = classDef.getProperties(); + + // for each property, determine if it has a default value + for (Map.Entry entry : propertyDefs.entrySet()) + { + String defaultValue = entry.getValue().getDefaultValue(); + if (defaultValue != null) + { + if (existingValues == null) + { + existingValues = getTaskProperties(instance); + } + if (existingValues.get(entry.getKey()) == null) + { + defaultValues.put(entry.getKey(), defaultValue); + } + } + } + + // assign the default values to the task + if (defaultValues.size() > 0) + { + setTaskProperties(instance, defaultValues); + } + } + + /** + * Set Task Outcome based on specified Transition + * + * @param instance task instance + * @param transition transition + */ + protected void setTaskOutcome(TaskInstance instance, Transition transition) + { + Map outcome = new HashMap(); + outcome.put(WorkflowModel.PROP_OUTCOME, transition.getName()); + setTaskProperties(instance, outcome); + } + + /** + * Convert a Repository association to JBPMNodeList or JBPMNode + * + * @param isMany true => force conversion to list + * @param value value to convert + * @return JBPMNodeList or JBPMNode + */ + private Serializable convertNodeRefs(boolean isMany, Serializable value) + { + if (value instanceof NodeRef) + { + if (isMany) + { + // convert single node ref to list of node refs + JBPMNodeList values = new JBPMNodeList(); + values.add(new JBPMNode((NodeRef)value, serviceRegistry)); + value = (Serializable)values; + } + else + { + value = new JBPMNode((NodeRef)value, serviceRegistry); + } + } + else if (value instanceof List) + { + if (isMany) + { + JBPMNodeList values = new JBPMNodeList(); + for (NodeRef nodeRef : (List)value) + { + values.add(new JBPMNode(nodeRef, serviceRegistry)); + } + value = (Serializable)values; + } + else + { + List nodeRefs = (List)value; + value = (nodeRefs.size() == 0 ? null : new JBPMNode(nodeRefs.get(0), serviceRegistry)); + } + } + + return value; + } + + /** + * Convert authority name to an Alfresco Authority + * + * @param names the authority names to convert + * @return the Alfresco authorities + */ + private NodeRef mapNameToAuthority(String name) + { + NodeRef authority = null; + if (name != null) + { + // TODO: Should this be an exception? + if (personService.personExists(name)) + { + authority = personService.getPerson(name); + } + } + return authority; + } + + /** + * Get an I18N Label for a workflow item + * + * @param displayId message resource id lookup + * @param labelKey label to lookup (title or description) + * @param defaultLabel default value if not found in message resource bundle + * @return the label + */ + private String getLabel(String displayId, String labelKey, String defaultLabel) + { + String key = StringUtils.replace(displayId, ":", "_"); + key += "." + labelKey; + String label = I18NUtil.getMessage(key); + return (label == null) ? defaultLabel : label; + } + + + // + // Workflow Data Object Creation... + // + + /** + * Creates a Workflow Path + * + * @param token JBoss JBPM Token + * @return Workflow Path + */ + protected WorkflowPath createWorkflowPath(Token token) + { + WorkflowPath path = new WorkflowPath(); + String tokenId = token.getFullName().replace("/", WORKFLOW_TOKEN_SEPERATOR); + path.id = createGlobalId(token.getProcessInstance().getId() + WORKFLOW_PATH_SEPERATOR + tokenId); + path.instance = createWorkflowInstance(token.getProcessInstance()); + path.node = createWorkflowNode(token.getNode()); + path.active = !token.hasEnded(); + return path; + } + + /** + * Creates a Workflow Node + * + * @param node JBoss JBPM Node + * @return Workflow Node + */ + @SuppressWarnings("unchecked") + protected WorkflowNode createWorkflowNode(Node node) + { + String processName = node.getProcessDefinition().getName(); + WorkflowNode workflowNode = new WorkflowNode(); + workflowNode.name = node.getName(); + workflowNode.title = getLabel(processName + ".node." + workflowNode.name, TITLE_LABEL, workflowNode.name); + workflowNode.description = getLabel(processName + ".node." + workflowNode.name, DESC_LABEL, workflowNode.title); + if (node instanceof HibernateProxy) + { + Node realNode = (Node)((HibernateProxy)node).getHibernateLazyInitializer().getImplementation(); + workflowNode.type = realNode.getClass().getSimpleName(); + } + else + { + workflowNode.type = node.getClass().getSimpleName(); + } + // TODO: Is there a formal way of determing if task node? + workflowNode.isTaskNode = workflowNode.type.equals("TaskNode"); + List transitions = node.getLeavingTransitions(); + workflowNode.transitions = new WorkflowTransition[(transitions == null) ? 0 : transitions.size()]; + if (transitions != null) + { + int i = 0; + for (Transition transition : (List)transitions) + { + workflowNode.transitions[i++] = createWorkflowTransition(transition); + } + } + return workflowNode; + } + + /** + * Create a Workflow Transition + * + * @param transition JBoss JBPM Transition + * @return Workflow Transition + */ + protected WorkflowTransition createWorkflowTransition(Transition transition) + { + WorkflowTransition workflowTransition = new WorkflowTransition(); + workflowTransition.id = transition.getName(); + Node node = transition.getFrom(); + workflowTransition.isDefault = node.getDefaultLeavingTransition().equals(transition); + if (workflowTransition.id.length() == 0) + { + workflowTransition.title = getLabel(DEFAULT_TRANSITION_LABEL, TITLE_LABEL, workflowTransition.id); + workflowTransition.description = getLabel(DEFAULT_TRANSITION_LABEL, DESC_LABEL, workflowTransition.title); + } + else + { + String nodeName = node.getName(); + String processName = node.getProcessDefinition().getName(); + workflowTransition.title = getLabel(processName + ".node." + nodeName + ".transition." + workflowTransition.id, TITLE_LABEL, workflowTransition.id); + workflowTransition.description = getLabel(processName + ".node." + nodeName + ".transition." + workflowTransition.id, DESC_LABEL, workflowTransition.title); + } + return workflowTransition; + } + + /** + * Creates a Workflow Instance + * + * @param instance JBoss JBPM Process Instance + * @return Workflow instance + */ + protected WorkflowInstance createWorkflowInstance(ProcessInstance instance) + { + WorkflowInstance workflowInstance = new WorkflowInstance(); + workflowInstance.id = createGlobalId(new Long(instance.getId()).toString()); + workflowInstance.definition = createWorkflowDefinition(instance.getProcessDefinition()); + workflowInstance.active = !instance.hasEnded(); + JBPMNode initiator = (JBPMNode)instance.getContextInstance().getVariable("initiator"); + if (initiator != null) + { + workflowInstance.initiator = initiator.getNodeRef(); + } + workflowInstance.startDate = instance.getStart(); + workflowInstance.endDate = instance.getEnd(); + return workflowInstance; + } + + /** + * Creates a Workflow Definition + * + * @param definition JBoss Process Definition + * @return Workflow Definition + */ + protected WorkflowDefinition createWorkflowDefinition(ProcessDefinition definition) + { + WorkflowDefinition workflowDef = new WorkflowDefinition(); + workflowDef.id = createGlobalId(new Long(definition.getId()).toString()); + workflowDef.name = definition.getName(); + workflowDef.title = getLabel(workflowDef.name + ".workflow", TITLE_LABEL, workflowDef.name); + workflowDef.description = getLabel(workflowDef.name + ".workflow", DESC_LABEL, workflowDef.title); + workflowDef.version = new Integer(definition.getVersion()).toString(); + Task startTask = definition.getTaskMgmtDefinition().getStartTask(); + if (startTask != null) + { + workflowDef.startTaskDefinition = createWorkflowTaskDefinition(startTask); + } + return workflowDef; + } + + /** + * Creates a Workflow Task + * + * @param task JBoss Task Instance + * @return Workflow Task + */ + protected WorkflowTask createWorkflowTask(TaskInstance task) + { + String processName = task.getTask().getProcessDefinition().getName(); + + WorkflowTask workflowTask = new WorkflowTask(); + workflowTask.id = createGlobalId(new Long(task.getId()).toString()); + workflowTask.name = task.getName(); + workflowTask.path = createWorkflowPath(task.getToken()); + workflowTask.state = getWorkflowTaskState(task); + workflowTask.definition = createWorkflowTaskDefinition(task.getTask()); + workflowTask.properties = getTaskProperties(task); + workflowTask.title = getLabel(processName + ".task." + workflowTask.name, TITLE_LABEL, null); + if (workflowTask.title == null) + { + workflowTask.title = workflowTask.definition.metadata.getTitle(); + if (workflowTask.title == null) + { + workflowTask.title = workflowTask.name; + } + } + workflowTask.description = getLabel(processName + ".task." + workflowTask.name, DESC_LABEL, null); + if (workflowTask.description == null) + { + String description = workflowTask.definition.metadata.getDescription(); + workflowTask.description = (description == null) ? workflowTask.title : description; + } + return workflowTask; + } + + /** + * Creates a Workflow Task Definition + * + * @param task JBoss JBPM Task + * @return Workflow Task Definition + */ + protected WorkflowTaskDefinition createWorkflowTaskDefinition(Task task) + { + WorkflowTaskDefinition taskDef = new WorkflowTaskDefinition(); + taskDef.id = task.getName(); + Node node = (task.getStartState() == null ? task.getTaskNode() : task.getStartState()); + taskDef.node = createWorkflowNode(node); + taskDef.metadata = getTaskDefinition(task); + return taskDef; + } + + /** + * Creates a Workflow Deployment + * + * @param compiledDef compiled JBPM process definition + * @return workflow deployment + */ + protected WorkflowDeployment createWorkflowDeployment(CompiledProcessDefinition compiledDef) + { + WorkflowDeployment deployment = new WorkflowDeployment(); + deployment.definition = createWorkflowDefinition(compiledDef.def); + deployment.problems = compiledDef.problems; + return deployment; + } + + /** + * Get the Workflow Task State for the specified JBoss JBPM Task + * + * @param task task + * @return task state + */ + protected WorkflowTaskState getWorkflowTaskState(TaskInstance task) + { + if (task.hasEnded()) + { + return WorkflowTaskState.COMPLETED; + } + else + { + return WorkflowTaskState.IN_PROGRESS; + } + } + +} diff --git a/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngineTest.java b/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngineTest.java new file mode 100644 index 0000000000..0215db9270 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngineTest.java @@ -0,0 +1,469 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.workflow.jbpm; + +import java.io.IOException; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.i18n.I18NUtil; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.workflow.BPMEngineRegistry; +import org.alfresco.repo.workflow.TaskComponent; +import org.alfresco.repo.workflow.WorkflowComponent; +import org.alfresco.repo.workflow.WorkflowModel; +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.workflow.WorkflowDefinition; +import org.alfresco.service.cmr.workflow.WorkflowDeployment; +import org.alfresco.service.cmr.workflow.WorkflowException; +import org.alfresco.service.cmr.workflow.WorkflowInstance; +import org.alfresco.service.cmr.workflow.WorkflowPath; +import org.alfresco.service.cmr.workflow.WorkflowTask; +import org.alfresco.service.cmr.workflow.WorkflowTaskState; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.BaseSpringTest; +import org.springframework.core.io.ClassPathResource; + + +/** + * JBPM Engine Tests + * + * @author davidc + */ +public class JBPMEngineTest extends BaseSpringTest +{ + AuthenticationComponent authenticationComponent; + NodeService nodeService; + WorkflowComponent workflowComponent; + TaskComponent taskComponent; + WorkflowDefinition testWorkflowDef; + NodeRef testNodeRef; + + + @Override + protected void onSetUpInTransaction() throws Exception + { + // run as system + authenticationComponent = (AuthenticationComponent)applicationContext.getBean("authenticationComponent"); + authenticationComponent.setCurrentUser("admin"); + + BPMEngineRegistry registry = (BPMEngineRegistry)applicationContext.getBean("bpm_engineRegistry"); + workflowComponent = registry.getWorkflowComponent("jbpm"); + taskComponent = registry.getTaskComponent("jbpm"); + + // deploy test process messages + I18NUtil.registerResourceBundle("org/alfresco/repo/workflow/jbpm/test-messages"); + + // deploy test process definition + ClassPathResource processDef = new ClassPathResource("org/alfresco/repo/workflow/jbpm/test_processdefinition.xml"); + assertFalse(workflowComponent.isDefinitionDeployed(processDef.getInputStream(), MimetypeMap.MIMETYPE_XML)); + WorkflowDeployment deployment = workflowComponent.deployDefinition(processDef.getInputStream(), MimetypeMap.MIMETYPE_XML); + testWorkflowDef = deployment.definition; + assertNotNull(testWorkflowDef); + assertEquals("test", testWorkflowDef.name); + assertEquals("1", testWorkflowDef.version); + assertTrue(workflowComponent.isDefinitionDeployed(processDef.getInputStream(), MimetypeMap.MIMETYPE_XML)); + + // get valid node ref + nodeService = (NodeService)applicationContext.getBean(ServiceRegistry.NODE_SERVICE.getLocalName()); + testNodeRef = nodeService.getRootNode(new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "spacesStore")); + nodeService.setProperty(testNodeRef, ContentModel.PROP_CREATED, new Date()); + } + + + @Override + protected void onTearDownInTransaction() + { + authenticationComponent.clearCurrentSecurityContext(); + } + + + public void testGetWorkflowDefinitions() + { + List workflowDefs = workflowComponent.getDefinitions(); + assertNotNull(workflowDefs); + assertTrue(workflowDefs.size() > 0); + } + + + public void testDeployWorkflow() throws Exception + { + ClassPathResource processDef = new ClassPathResource("org/alfresco/repo/workflow/jbpm/test_processdefinition.xml"); + WorkflowDeployment deployment = workflowComponent.deployDefinition(processDef.getInputStream(), MimetypeMap.MIMETYPE_XML); + testWorkflowDef = deployment.definition; + assertNotNull(testWorkflowDef); + assertEquals("test", testWorkflowDef.name); + assertEquals("2", testWorkflowDef.version); + } + + + public void testStartWorkflow() + { + try + { + @SuppressWarnings("unused") WorkflowPath path = workflowComponent.startWorkflow("norfolknchance", null); + fail("Failed to catch invalid definition id"); + } + catch(WorkflowException e) + { + } + + // TODO: Determine why process definition is loaded, even though it doesn't exist +// try +// { +// @SuppressWarnings("unused") WorkflowPosition pos = workflowComponent.startProcess("1000", null); +// fail("Failed to catch workflow definition id that does not exist"); +// } +// catch(WorkflowException e) +// { +// } + + WorkflowDefinition workflowDef = getTestDefinition(); + WorkflowPath path = workflowComponent.startWorkflow(workflowDef.id, null); + assertNotNull(path); + assertTrue(path.id.endsWith("-@")); + assertNotNull(path.node); + assertNotNull(path.instance); + assertEquals(workflowDef.id, path.instance.definition.id); + } + + + public void testStartWorkflowParameters() + { + WorkflowDefinition workflowDef = getTestDefinition(); + + Map params = new HashMap(); + params.put(WorkflowModel.PROP_TASK_ID, 3); // protected - shouldn't be written + params.put(WorkflowModel.PROP_DUE_DATE, new Date()); // task instance field + params.put(WorkflowModel.PROP_PRIORITY, 1); // task instance field + params.put(WorkflowModel.PROP_PERCENT_COMPLETE, 10); // context variable + params.put(QName.createQName("", "Message"), "Hello World"); // context variable outside of task definition + params.put(QName.createQName("", "Array"), new String[] { "one", "two" }); // context variable outside of task definition + params.put(QName.createQName("", "NodeRef"), new NodeRef("workspace://1/1001")); // context variable outside of task definition + params.put(ContentModel.PROP_OWNER, "admin"); // task assignment + + WorkflowPath path = workflowComponent.startWorkflow(workflowDef.id, params); + assertNotNull(path); + assertTrue(path.id.endsWith("-@")); + assertNotNull(path.node); + assertNotNull(path.instance); + assertEquals(workflowDef.id, path.instance.definition.id); + List tasks1 = workflowComponent.getTasksForWorkflowPath(path.id); + assertNotNull(tasks1); + assertEquals(1, tasks1.size()); + + WorkflowTask task = tasks1.get(0); + assertTrue(task.properties.containsKey(WorkflowModel.PROP_TASK_ID)); + assertTrue(task.properties.containsKey(WorkflowModel.PROP_DUE_DATE)); + assertTrue(task.properties.containsKey(WorkflowModel.PROP_PRIORITY)); + assertTrue(task.properties.containsKey(WorkflowModel.PROP_PERCENT_COMPLETE)); + assertTrue(task.properties.containsKey(ContentModel.PROP_OWNER)); + + NodeRef initiator = path.instance.initiator; + String initiatorUsername = (String)nodeService.getProperty(initiator, ContentModel.PROP_USERNAME); + assertEquals("admin", initiatorUsername); + } + + + public void testUpdateTask() + { + WorkflowDefinition workflowDef = getTestDefinition(); + + Map params = new HashMap(); + params.put(WorkflowModel.PROP_TASK_ID, 3); // protected - shouldn't be written + params.put(WorkflowModel.PROP_DUE_DATE, new Date()); // task instance field + params.put(WorkflowModel.PROP_PRIORITY, 1); // task instance field + params.put(WorkflowModel.PROP_PERCENT_COMPLETE, 10); // context variable + params.put(QName.createQName("", "Message"), "Hello World"); // context variable outside of task definition + params.put(QName.createQName("", "Array"), new String[] { "one", "two" }); // context variable outside of task definition + params.put(QName.createQName("", "NodeRef"), new NodeRef("workspace://1/1001")); // context variable outside of task definition + params.put(ContentModel.PROP_OWNER, "admin"); // task assignment + + WorkflowPath path = workflowComponent.startWorkflow(workflowDef.id, params); + assertNotNull(path); + assertTrue(path.id.endsWith("-@")); + assertNotNull(path.node); + assertNotNull(path.instance); + assertEquals(workflowDef.id, path.instance.definition.id); + List tasks1 = workflowComponent.getTasksForWorkflowPath(path.id); + assertNotNull(tasks1); + assertEquals(1, tasks1.size()); + + WorkflowTask task = tasks1.get(0); + assertTrue(task.properties.containsKey(WorkflowModel.PROP_TASK_ID)); + assertTrue(task.properties.containsKey(WorkflowModel.PROP_DUE_DATE)); + assertTrue(task.properties.containsKey(WorkflowModel.PROP_PRIORITY)); + assertTrue(task.properties.containsKey(WorkflowModel.PROP_PERCENT_COMPLETE)); + assertTrue(task.properties.containsKey(ContentModel.PROP_OWNER)); + + // update with null parameters + try + { + WorkflowTask taskU1 = taskComponent.updateTask(task.id, null, null, null); + assertNotNull(taskU1); + } + catch(Throwable e) + { + fail("Task update failed with null parameters"); + } + + // update property value + Map updateProperties2 = new HashMap(); + updateProperties2.put(WorkflowModel.PROP_PERCENT_COMPLETE, 100); + WorkflowTask taskU2 = taskComponent.updateTask(task.id, updateProperties2, null, null); + assertEquals(100, taskU2.properties.get(WorkflowModel.PROP_PERCENT_COMPLETE)); + + // add to assocation + QName assocName = QName.createQName("", "TestAssoc"); + List toAdd = new ArrayList(); + toAdd.add(new NodeRef("workspace://1/1001")); + toAdd.add(new NodeRef("workspace://1/1002")); + toAdd.add(new NodeRef("workspace://1/1003")); + Map> addAssocs = new HashMap>(); + addAssocs.put(assocName, toAdd); + WorkflowTask taskU3 = taskComponent.updateTask(task.id, null, addAssocs, null); + assertNotNull(taskU3.properties.get(assocName)); + assertEquals(3, ((List)taskU3.properties.get(assocName)).size()); + + // add to assocation again + List toAddAgain = new ArrayList(); + toAddAgain.add(new NodeRef("workspace://1/1004")); + toAddAgain.add(new NodeRef("workspace://1/1005")); + Map> addAssocsAgain = new HashMap>(); + addAssocsAgain.put(assocName, toAddAgain); + WorkflowTask taskU4 = taskComponent.updateTask(task.id, null, addAssocsAgain, null); + assertNotNull(taskU4.properties.get(assocName)); + assertEquals(5, ((List)taskU4.properties.get(assocName)).size()); + + // remove assocation + List toRemove = new ArrayList(); + toRemove.add(new NodeRef("workspace://1/1002")); + toRemove.add(new NodeRef("workspace://1/1003")); + Map> removeAssocs = new HashMap>(); + removeAssocs.put(assocName, toRemove); + WorkflowTask taskU5 = taskComponent.updateTask(task.id, null, null, removeAssocs); + assertNotNull(taskU5.properties.get(assocName)); + assertEquals(3, ((List)taskU5.properties.get(assocName)).size()); + } + + + public void testGetWorkflowInstances() + { + WorkflowDefinition workflowDef = getTestDefinition(); + workflowComponent.startWorkflow(workflowDef.id, null); + workflowComponent.startWorkflow(workflowDef.id, null); + List instances = workflowComponent.getActiveWorkflows(workflowDef.id); + assertNotNull(instances); + assertEquals(2, instances.size()); + for (WorkflowInstance instance : instances) + { + assertEquals(workflowDef.id, instance.definition.id); + } + } + + + public void testGetPositions() + { + WorkflowDefinition workflowDef = getTestDefinition(); + workflowComponent.startWorkflow(workflowDef.id, null); + List instances = workflowComponent.getActiveWorkflows(workflowDef.id); + assertNotNull(instances); + assertEquals(1, instances.size()); + List paths = workflowComponent.getWorkflowPaths(instances.get(0).id); + assertNotNull(paths); + assertEquals(1, paths.size()); + assertEquals(instances.get(0).id, paths.get(0).instance.id); + assertTrue(paths.get(0).id.endsWith("-@")); + } + + + public void testCancelWorkflowInstance() + { + WorkflowDefinition workflowDef = getTestDefinition(); + workflowComponent.startWorkflow(workflowDef.id, null); + List instances1 = workflowComponent.getActiveWorkflows(workflowDef.id); + assertNotNull(instances1); + assertEquals(1, instances1.size()); + WorkflowInstance cancelledInstance = workflowComponent.cancelWorkflow(instances1.get(0).id); + assertNotNull(cancelledInstance); + assertFalse(cancelledInstance.active); + List instances2 = workflowComponent.getActiveWorkflows(workflowDef.id); + assertNotNull(instances2); + assertEquals(0, instances2.size()); + } + + + public void testSignal() + { + WorkflowDefinition workflowDef = getTestDefinition(); + WorkflowPath path = workflowComponent.startWorkflow(workflowDef.id, null); + assertNotNull(path); + WorkflowPath updatedPath = workflowComponent.signal(path.id, path.node.transitions[1].id); + assertNotNull(updatedPath); + } + + + public void testGetAssignedTasks() + { + WorkflowDefinition workflowDef = getTestDefinition(); + Map parameters = new HashMap(); + parameters.put(QName.createQName(NamespaceService.DEFAULT_URI, "reviewer"), "admin"); + parameters.put(QName.createQName(NamespaceService.DEFAULT_URI, "testNode"), testNodeRef); + WorkflowPath path = workflowComponent.startWorkflow(workflowDef.id, parameters); + assertNotNull(path); + assertNotNull(path); + List tasks = workflowComponent.getTasksForWorkflowPath(path.id); + assertNotNull(tasks); + assertEquals(1, tasks.size()); + WorkflowTask updatedTask = taskComponent.endTask(tasks.get(0).id, path.node.transitions[0].id); + assertNotNull(updatedTask); + List completedTasks = taskComponent.getAssignedTasks("admin", WorkflowTaskState.COMPLETED); + assertNotNull(completedTasks); + completedTasks = filterTasksByWorkflowInstance(completedTasks, path.instance.id); + assertEquals(1, completedTasks.size()); + List assignedTasks = taskComponent.getAssignedTasks("admin", WorkflowTaskState.IN_PROGRESS); + assertNotNull(assignedTasks); + assignedTasks = filterTasksByWorkflowInstance(assignedTasks, path.instance.id); + assertEquals(1, assignedTasks.size()); + assertEquals("review", assignedTasks.get(0).name); + } + + + public void testEndTask() + { + WorkflowDefinition workflowDef = getTestDefinition(); + Map parameters = new HashMap(); + parameters.put(QName.createQName(NamespaceService.DEFAULT_URI, "reviewer"), "admin"); + parameters.put(QName.createQName(NamespaceService.DEFAULT_URI, "testNode"), testNodeRef); + WorkflowPath path = workflowComponent.startWorkflow(workflowDef.id, parameters); + assertNotNull(path); + assertNotNull(path); + List tasks1 = workflowComponent.getTasksForWorkflowPath(path.id); + assertNotNull(tasks1); + assertEquals(1, tasks1.size()); + assertEquals(WorkflowTaskState.IN_PROGRESS, tasks1.get(0).state); + WorkflowTask updatedTask = taskComponent.endTask(tasks1.get(0).id, null); + assertNotNull(updatedTask); + assertEquals(WorkflowTaskState.COMPLETED, updatedTask.state); + List completedTasks = taskComponent.getAssignedTasks("admin", WorkflowTaskState.COMPLETED); + assertNotNull(completedTasks); + completedTasks = filterTasksByWorkflowInstance(completedTasks, path.instance.id); + assertEquals(1, completedTasks.size()); + assertEquals(WorkflowTaskState.COMPLETED, completedTasks.get(0).state); + } + + + public void testGetTask() + { + WorkflowDefinition workflowDef = getTestDefinition(); + Map parameters = new HashMap(); + parameters.put(QName.createQName(NamespaceService.DEFAULT_URI, "reviewer"), "admin"); + parameters.put(QName.createQName(NamespaceService.DEFAULT_URI, "testNode"), testNodeRef); + WorkflowPath path = workflowComponent.startWorkflow(workflowDef.id, parameters); + assertNotNull(path); + assertNotNull(path); + List tasks1 = workflowComponent.getTasksForWorkflowPath(path.id); + assertNotNull(tasks1); + assertEquals(1, tasks1.size()); + WorkflowTask getTask = taskComponent.getTaskById(tasks1.get(0).id); + assertNotNull(getTask); + assertEquals(getTask.id, tasks1.get(0).id); + } + + + public void testNodeRef() + { + WorkflowDefinition workflowDef = getTestDefinition(); + Map parameters = new HashMap(); + parameters.put(QName.createQName(NamespaceService.DEFAULT_URI, "reviewer"), "admin"); + parameters.put(QName.createQName(NamespaceService.DEFAULT_URI, "testNode"), testNodeRef); + WorkflowPath path = workflowComponent.startWorkflow(workflowDef.id, parameters); + assertNotNull(path); + List tasks1 = workflowComponent.getTasksForWorkflowPath(path.id); + assertNotNull(tasks1); + assertEquals(1, tasks1.size()); + assertEquals(WorkflowTaskState.IN_PROGRESS, tasks1.get(0).state); + WorkflowTask updatedTask = taskComponent.endTask(tasks1.get(0).id, null); + assertNotNull(updatedTask); + } + + + public void testScript() + throws IOException + { + // deploy test script definition + ClassPathResource processDef = new ClassPathResource("org/alfresco/repo/workflow/jbpm/test_script.xml"); + assertFalse(workflowComponent.isDefinitionDeployed(processDef.getInputStream(), MimetypeMap.MIMETYPE_XML)); + WorkflowDeployment deployment = workflowComponent.deployDefinition(processDef.getInputStream(), MimetypeMap.MIMETYPE_XML); + assertNotNull(deployment); + + WorkflowDefinition workflowDef = deployment.definition; + Map parameters = new HashMap(); + parameters.put(QName.createQName(NamespaceService.DEFAULT_URI, "testNode"), testNodeRef); + WorkflowPath path = workflowComponent.startWorkflow(workflowDef.id, parameters); + assertNotNull(path); + List tasks1 = workflowComponent.getTasksForWorkflowPath(path.id); + assertNotNull(tasks1); + assertEquals(1, tasks1.size()); + assertEquals(WorkflowTaskState.IN_PROGRESS, tasks1.get(0).state); + WorkflowTask updatedTask = taskComponent.endTask(tasks1.get(0).id, null); + assertNotNull(updatedTask); + } + + + /** + * Locate the Test Workflow Definition + * + * @return workflow definition + */ + private WorkflowDefinition getTestDefinition() + { + return testWorkflowDef; + } + + + /** + * Filter task list by workflow instance + * + * @param tasks + * @param processInstanceId + * @return + */ + private List filterTasksByWorkflowInstance(List tasks, String workflowInstanceId) + { + List filteredTasks = new ArrayList(); + for (WorkflowTask task : tasks) + { + if (task.path.instance.id.equals(workflowInstanceId)) + { + filteredTasks.add(task); + } + } + return filteredTasks; + } + +} diff --git a/source/java/org/alfresco/repo/workflow/jbpm/JBPMNode.java b/source/java/org/alfresco/repo/workflow/jbpm/JBPMNode.java new file mode 100644 index 0000000000..8fcde0d281 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/jbpm/JBPMNode.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.workflow.jbpm; + +import java.io.Serializable; +import java.util.Date; + +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; +import org.mozilla.javascript.Scriptable; + + +/** + * Scriptable Node suitable for JBPM Beanshell access + * + * TODO: This implementation derives from the JavaScript Alfresco Node. At + * some point we should look to having a script-independent node with various + * script-specific sub-types (and value conversions). + * + * @author davidc + */ +public class JBPMNode extends org.alfresco.repo.jscript.Node +{ + private static final long serialVersionUID = -826970280203254365L; + + /** + * Construct + * + * @param nodeRef node reference + * @param services services + */ + public JBPMNode(NodeRef nodeRef, ServiceRegistry services) + { + super(nodeRef, services, null); + } + + /* (non-Javadoc) + * @see org.alfresco.repo.jscript.Node#createValueConverter() + */ + @Override + protected NodeValueConverter createValueConverter() + { + return new JBPMNodeConverter(); + } + + /** + * Value converter for beanshell. + */ + private class JBPMNodeConverter extends org.alfresco.repo.jscript.Node.NodeValueConverter + { + @Override + public Serializable convertValueForRepo(Serializable value) + { + if (value instanceof Date) + { + return value; + } + else + { + return super.convertValueForRepo(value); + } + } + + @Override + public Serializable convertValueForScript(ServiceRegistry services, Scriptable scope, QName qname, Serializable value) + { + if (value instanceof NodeRef) + { + return new JBPMNode(((NodeRef)value), services); + } + else if (value instanceof Date) + { + return value; + } + else + { + return super.convertValueForScript(services, scope, qname, value); + } + } + } +} diff --git a/source/java/org/alfresco/repo/workflow/jbpm/JBPMNodeList.java b/source/java/org/alfresco/repo/workflow/jbpm/JBPMNodeList.java new file mode 100644 index 0000000000..7293641f3b --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/jbpm/JBPMNodeList.java @@ -0,0 +1,9 @@ +package org.alfresco.repo.workflow.jbpm; + +import java.util.ArrayList; + + +public class JBPMNodeList extends ArrayList +{ + private static final long serialVersionUID = 1376915749912156471L; +} diff --git a/source/java/org/alfresco/repo/workflow/jbpm/JBPMSpringTest.java b/source/java/org/alfresco/repo/workflow/jbpm/JBPMSpringTest.java index cd14d21b3b..5b83d5e93c 100644 --- a/source/java/org/alfresco/repo/workflow/jbpm/JBPMSpringTest.java +++ b/source/java/org/alfresco/repo/workflow/jbpm/JBPMSpringTest.java @@ -41,9 +41,9 @@ public class JBPMSpringTest extends BaseSpringTest //@Override - protected void xonSetUpInTransaction() throws Exception + protected void onSetUpInTransaction() throws Exception { - jbpmTemplate = (JbpmTemplate)applicationContext.getBean("jbpm.template"); + jbpmTemplate = (JbpmTemplate)applicationContext.getBean("jbpm_template"); descriptorService = (DescriptorService)applicationContext.getBean("DescriptorService"); } @@ -51,7 +51,7 @@ public class JBPMSpringTest extends BaseSpringTest { } - public void xtestHelloWorld() + public void testHelloWorld() throws Exception { // Between the 3 method calls below, all data is passed via the @@ -74,21 +74,21 @@ public class JBPMSpringTest extends BaseSpringTest theProcessInstanceContinuesWhenAnAsyncMessageIsReceived(); } - public void xtestStep0() + public void testStep0() throws Exception { deployProcessDefinition(); setComplete(); } - public void xtestStep1() + public void testStep1() throws Exception { processInstanceIsCreatedWhenUserSubmitsWebappForm(); setComplete(); } - public void xtestStep2() + public void testStep2() throws Exception { theProcessInstanceContinuesWhenAnAsyncMessageIsReceived(); diff --git a/source/java/org/alfresco/repo/workflow/jbpm/NodeConverter.java b/source/java/org/alfresco/repo/workflow/jbpm/NodeConverter.java new file mode 100644 index 0000000000..8f74a775b7 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/jbpm/NodeConverter.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.workflow.jbpm; + +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeRef; +import org.jbpm.context.exe.Converter; +import org.springframework.beans.factory.access.BeanFactoryLocator; +import org.springframework.beans.factory.access.BeanFactoryReference; +import org.springmodules.workflow.jbpm31.JbpmFactoryLocator; + + +/** + * jBPM Converter for transforming Alfresco Node to string and back + * + * @author davidc + */ +public class NodeConverter implements Converter +{ + + private static final long serialVersionUID = 1L; + private static BeanFactoryLocator jbpmFactoryLocator = new JbpmFactoryLocator(); + + + /* (non-Javadoc) + * @see org.jbpm.context.exe.Converter#supports(java.lang.Object) + */ + public boolean supports(Object value) + { + if (value == null) + { + return true; + } + return (value.getClass() == JBPMNode.class); + } + + /* (non-Javadoc) + * @see org.jbpm.context.exe.Converter#convert(java.lang.Object) + */ + public Object convert(Object o) + { + Object converted = null; + if (o != null) + { + converted = ((JBPMNode)o).getNodeRef().toString(); + } + return converted; + } + + /* (non-Javadoc) + * @see org.jbpm.context.exe.Converter#revert(java.lang.Object) + */ + public Object revert(Object o) + { + Object reverted = null; + if (o != null) + { + BeanFactoryReference factory = jbpmFactoryLocator.useBeanFactory(null); + ServiceRegistry serviceRegistry = (ServiceRegistry)factory.getFactory().getBean(ServiceRegistry.SERVICE_REGISTRY); + reverted = new JBPMNode(new NodeRef((String)o), serviceRegistry); + } + return reverted; + } + +} diff --git a/source/java/org/alfresco/repo/workflow/jbpm/NodeListConverter.java b/source/java/org/alfresco/repo/workflow/jbpm/NodeListConverter.java new file mode 100644 index 0000000000..fd94dc2d1c --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/jbpm/NodeListConverter.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.workflow.jbpm; + +import java.util.ArrayList; +import java.util.List; + +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeRef; +import org.jbpm.context.exe.converter.SerializableToByteArrayConverter; +import org.springframework.beans.factory.access.BeanFactoryLocator; +import org.springframework.beans.factory.access.BeanFactoryReference; +import org.springmodules.workflow.jbpm31.JbpmFactoryLocator; + + +/** + * jBPM Converter for transforming Alfresco Node to string and back + * + * @author davidc + */ +public class NodeListConverter extends SerializableToByteArrayConverter +{ + + private static final long serialVersionUID = 1L; + private static BeanFactoryLocator jbpmFactoryLocator = new JbpmFactoryLocator(); + + + /* (non-Javadoc) + * @see org.jbpm.context.exe.Converter#supports(java.lang.Object) + */ + public boolean supports(Object value) + { + if (value == null) + { + return true; + } + return (value.getClass() == JBPMNodeList.class); + } + + /* (non-Javadoc) + * @see org.jbpm.context.exe.Converter#convert(java.lang.Object) + */ + public Object convert(Object o) + { + Object converted = null; + if (o != null) + { + JBPMNodeList nodes = (JBPMNodeList)o; + List values = new ArrayList(nodes.size()); + for (JBPMNode node : nodes) + { + values.add(node.getNodeRef()); + } + converted = super.convert(values); + } + return converted; + } + + /* (non-Javadoc) + * @see org.jbpm.context.exe.Converter#revert(java.lang.Object) + */ + @SuppressWarnings("unchecked") + public Object revert(Object o) + { + Object reverted = null; + if (o != null) + { + List nodeRefs = (List)super.revert(o); + BeanFactoryReference factory = jbpmFactoryLocator.useBeanFactory(null); + ServiceRegistry serviceRegistry = (ServiceRegistry)factory.getFactory().getBean(ServiceRegistry.SERVICE_REGISTRY); + + JBPMNodeList nodes = new JBPMNodeList(); + for (NodeRef nodeRef : nodeRefs) + { + nodes.add(new JBPMNode(nodeRef, serviceRegistry)); + } + reverted = nodes; + } + return reverted; + } + +} diff --git a/source/java/org/alfresco/repo/workflow/jbpm/NodeListConverterTest.java b/source/java/org/alfresco/repo/workflow/jbpm/NodeListConverterTest.java new file mode 100644 index 0000000000..52c478ceb4 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/jbpm/NodeListConverterTest.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.workflow.jbpm; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.workflow.BPMEngineRegistry; +import org.alfresco.repo.workflow.TaskComponent; +import org.alfresco.repo.workflow.WorkflowComponent; +import org.alfresco.repo.workflow.WorkflowModel; +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.security.PersonService; +import org.alfresco.service.cmr.workflow.WorkflowDefinition; +import org.alfresco.service.cmr.workflow.WorkflowDeployment; +import org.alfresco.service.cmr.workflow.WorkflowPath; +import org.alfresco.service.cmr.workflow.WorkflowTask; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.BaseSpringTest; +import org.springframework.core.io.ClassPathResource; + + +/** + * JBPM Engine Tests + * + * @author davidc + */ +public class NodeListConverterTest extends BaseSpringTest +{ + AuthenticationComponent authenticationComponent; + PersonService personService; + WorkflowComponent workflowComponent; + TaskComponent taskComponent; + WorkflowDefinition testWorkflowDef; + NodeRef testNodeRef; + + private static String taskId = null; + + + @Override + protected void onSetUpInTransaction() throws Exception + { + personService = (PersonService)applicationContext.getBean("personService"); + BPMEngineRegistry registry = (BPMEngineRegistry)applicationContext.getBean("bpm_engineRegistry"); + workflowComponent = registry.getWorkflowComponent("jbpm"); + taskComponent = registry.getTaskComponent("jbpm"); + + // deploy latest review and approve process definition + ClassPathResource processDef = new ClassPathResource("org/alfresco/repo/workflow/jbpm/review_and_approve_processdefinition.xml"); + WorkflowDeployment deployment = workflowComponent.deployDefinition(processDef.getInputStream(), MimetypeMap.MIMETYPE_XML); + testWorkflowDef = deployment.definition; + assertNotNull(testWorkflowDef); + + // run as system + authenticationComponent = (AuthenticationComponent)applicationContext.getBean("authenticationComponent"); + authenticationComponent.setSystemUserAsCurrentUser(); + + // get valid node ref + NodeService nodeService = (NodeService)applicationContext.getBean(ServiceRegistry.NODE_SERVICE.getLocalName()); + testNodeRef = nodeService.getRootNode(new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "spacesStore")); + } + + + public void testStep1Start() + { + Map params = new HashMap(); + params.put(WorkflowModel.ASSOC_PACKAGE, testNodeRef); + Date reviewDueDate = new Date(); + params.put(QName.createQName("http://www.alfresco.org/model/workflow/1.0", "reviewDueDate"), reviewDueDate); + NodeRef reviewer = personService.getPerson("admin"); + params.put(QName.createQName("http://www.alfresco.org/model/workflow/1.0", "reviewer"), reviewer); + + WorkflowPath path = workflowComponent.startWorkflow(testWorkflowDef.id, params); + assertNotNull(path); + List tasks1 = workflowComponent.getTasksForWorkflowPath(path.id); + assertNotNull(tasks1); + assertEquals(1, tasks1.size()); + + setComplete(); + taskId = tasks1.get(0).id; + } + + + public void testSetNodeRefList() + { + List nodeRefs = new ArrayList(); + nodeRefs.add(testNodeRef); + nodeRefs.add(testNodeRef); + Map params = new HashMap(); + params.put(WorkflowModel.PROP_COMPLETED_ITEMS, (Serializable)nodeRefs); + + WorkflowTask task = taskComponent.getTaskById(taskId); + assertNull(task.properties.get(WorkflowModel.PROP_COMPLETED_ITEMS)); + + WorkflowTask updatedTask = taskComponent.updateTask(taskId, params, null, null); + assertNotNull(updatedTask); + assertTrue(updatedTask.properties.containsKey(WorkflowModel.PROP_COMPLETED_ITEMS)); + assertEquals(2, ((List)updatedTask.properties.get(WorkflowModel.PROP_COMPLETED_ITEMS)).size()); + + setComplete(); + } + + + public void testUpdateNodeRefList() + { + List nodeRefs = new ArrayList(); +// nodeRefs.add(testNodeRef); + Map params = new HashMap(); + params.put(WorkflowModel.PROP_COMPLETED_ITEMS, (Serializable)nodeRefs); + +// WorkflowTask task = taskComponent.getTaskById(taskId); +// assertNotNull(task); +// assertTrue(task.properties.containsKey(WorkflowModel.PROP_COMPLETED_ITEMS)); +// assertEquals(2, ((List)task.properties.get(WorkflowModel.PROP_COMPLETED_ITEMS)).size()); + + WorkflowTask updatedTask = taskComponent.updateTask(taskId, params, null, null); + assertNotNull(updatedTask); + assertTrue(updatedTask.properties.containsKey(WorkflowModel.PROP_COMPLETED_ITEMS)); + assertEquals(0, ((List)updatedTask.properties.get(WorkflowModel.PROP_COMPLETED_ITEMS)).size()); + + setComplete(); + } + +} diff --git a/source/java/org/alfresco/repo/workflow/jbpm/ReviewAndApproveTest.java b/source/java/org/alfresco/repo/workflow/jbpm/ReviewAndApproveTest.java new file mode 100644 index 0000000000..a8f0379d5e --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/jbpm/ReviewAndApproveTest.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.workflow.jbpm; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.workflow.BPMEngineRegistry; +import org.alfresco.repo.workflow.TaskComponent; +import org.alfresco.repo.workflow.WorkflowComponent; +import org.alfresco.repo.workflow.WorkflowModel; +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.security.PersonService; +import org.alfresco.service.cmr.workflow.WorkflowDefinition; +import org.alfresco.service.cmr.workflow.WorkflowDeployment; +import org.alfresco.service.cmr.workflow.WorkflowPath; +import org.alfresco.service.cmr.workflow.WorkflowTask; +import org.alfresco.service.cmr.workflow.WorkflowTaskState; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.BaseSpringTest; +import org.springframework.core.io.ClassPathResource; + + +/** + * Review and Approve workflow specific Tests + * + * @author davidc + */ +public class ReviewAndApproveTest extends BaseSpringTest +{ + AuthenticationComponent authenticationComponent; + PersonService personService; + WorkflowComponent workflowComponent; + TaskComponent taskComponent; + WorkflowDefinition testWorkflowDef; + NodeRef testNodeRef; + + + + @Override + protected void onSetUpInTransaction() throws Exception + { + personService = (PersonService)applicationContext.getBean("personService"); + BPMEngineRegistry registry = (BPMEngineRegistry)applicationContext.getBean("bpm_engineRegistry"); + workflowComponent = registry.getWorkflowComponent("jbpm"); + taskComponent = registry.getTaskComponent("jbpm"); + + // deploy latest review and approve process definition + ClassPathResource processDef = new ClassPathResource("org/alfresco/repo/workflow/jbpm/review_and_approve_processdefinition.xml"); + WorkflowDeployment deployment = workflowComponent.deployDefinition(processDef.getInputStream(), MimetypeMap.MIMETYPE_XML); + testWorkflowDef = deployment.definition; + assertNotNull(testWorkflowDef); + + // run as system + authenticationComponent = (AuthenticationComponent)applicationContext.getBean("authenticationComponent"); + authenticationComponent.setSystemUserAsCurrentUser(); + + // get valid node ref + NodeService nodeService = (NodeService)applicationContext.getBean(ServiceRegistry.NODE_SERVICE.getLocalName()); + testNodeRef = nodeService.getRootNode(new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "spacesStore")); + } + + @Override + protected void onTearDownInTransaction() + { + authenticationComponent.clearCurrentSecurityContext(); + } + + + public void testSubmitForReview() + { + WorkflowDefinition workflowDef = testWorkflowDef; + + Map params = new HashMap(); + params.put(WorkflowModel.ASSOC_PACKAGE, testNodeRef); + Date reviewDueDate = new Date(); + params.put(QName.createQName("http://www.alfresco.org/model/workflow/1.0", "reviewDueDate"), reviewDueDate); + NodeRef reviewer = personService.getPerson("admin"); + params.put(QName.createQName("http://www.alfresco.org/model/workflow/1.0", "reviewer"), reviewer); + + WorkflowPath path = workflowComponent.startWorkflow(workflowDef.id, params); + assertNotNull(path); + List tasks1 = workflowComponent.getTasksForWorkflowPath(path.id); + assertNotNull(tasks1); + assertEquals(1, tasks1.size()); + + WorkflowTask task = tasks1.get(0); + assertTrue(task.properties.containsKey(WorkflowModel.ASSOC_PACKAGE)); + WorkflowTask endedTask = taskComponent.endTask(task.id, null); + assertNotNull(endedTask); + assertTrue(endedTask.properties.containsKey(WorkflowModel.PROP_OUTCOME)); + assertEquals("", endedTask.properties.get(WorkflowModel.PROP_OUTCOME)); + + List assignedTasks = taskComponent.getAssignedTasks("admin", WorkflowTaskState.IN_PROGRESS); + assertNotNull(assignedTasks); + assignedTasks = filterTasksByWorkflowInstance(assignedTasks, path.instance.id); + + assertEquals(testNodeRef, assignedTasks.get(0).properties.get(WorkflowModel.ASSOC_PACKAGE)); + assertEquals(reviewDueDate, assignedTasks.get(0).properties.get(WorkflowModel.PROP_DUE_DATE)); + } + + public void testCompletedItems() + { + WorkflowDefinition workflowDef = testWorkflowDef; + + List nodeRefs = new ArrayList(); + nodeRefs.add(testNodeRef); + nodeRefs.add(testNodeRef); + + Map params = new HashMap(); + params.put(WorkflowModel.ASSOC_PACKAGE, testNodeRef); + params.put(WorkflowModel.PROP_COMPLETED_ITEMS, (Serializable)nodeRefs); + Date reviewDueDate = new Date(); + params.put(QName.createQName("http://www.alfresco.org/model/workflow/1.0", "reviewDueDate"), reviewDueDate); + NodeRef reviewer = personService.getPerson("admin"); + params.put(QName.createQName("http://www.alfresco.org/model/workflow/1.0", "reviewer"), reviewer); + + WorkflowPath path = workflowComponent.startWorkflow(workflowDef.id, params); + assertNotNull(path); + List tasks1 = workflowComponent.getTasksForWorkflowPath(path.id); + assertNotNull(tasks1); + assertEquals(1, tasks1.size()); + + WorkflowTask task = tasks1.get(0); + assertTrue(task.properties.containsKey(WorkflowModel.PROP_COMPLETED_ITEMS)); + assertEquals(2, ((List)task.properties.get(WorkflowModel.PROP_COMPLETED_ITEMS)).size()); + } + + + /** + * Filter task list by workflow instance + * + * @param tasks + * @param processInstanceId + * @return + */ + private List filterTasksByWorkflowInstance(List tasks, String workflowInstanceId) + { + List filteredTasks = new ArrayList(); + for (WorkflowTask task : tasks) + { + if (task.path.instance.id.equals(workflowInstanceId)) + { + filteredTasks.add(task); + } + } + return filteredTasks; + } + +} diff --git a/source/java/org/alfresco/repo/workflow/jbpm/WorkflowTaskInstance.hbm.xml b/source/java/org/alfresco/repo/workflow/jbpm/WorkflowTaskInstance.hbm.xml new file mode 100644 index 0000000000..8806b04c1c --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/jbpm/WorkflowTaskInstance.hbm.xml @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/source/java/org/alfresco/repo/workflow/jbpm/WorkflowTaskInstance.java b/source/java/org/alfresco/repo/workflow/jbpm/WorkflowTaskInstance.java new file mode 100644 index 0000000000..33559d47ee --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/jbpm/WorkflowTaskInstance.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.workflow.jbpm; + +import org.alfresco.service.cmr.workflow.WorkflowException; +import org.jbpm.graph.def.Transition; +import org.jbpm.graph.exe.ExecutionContext; +import org.jbpm.taskmgmt.exe.TaskInstance; +import org.springframework.beans.factory.access.BeanFactoryLocator; +import org.springframework.beans.factory.access.BeanFactoryReference; +import org.springmodules.workflow.jbpm31.JbpmFactoryLocator; + + +/** + * Alfresco specific implementation of a jBPM task instance + * + * @author davidc + */ +public class WorkflowTaskInstance extends TaskInstance +{ + private static final long serialVersionUID = 6824116036569411964L; + + /** Alfresco JBPM Engine */ + private static JBPMEngine jbpmEngine = null; + + /** + * Gets the JBPM Engine instance + * + * @return JBPM Engine + */ + private JBPMEngine getJBPMEngine() + { + if (jbpmEngine == null) + { + BeanFactoryLocator factoryLocator = new JbpmFactoryLocator(); + BeanFactoryReference factory = factoryLocator.useBeanFactory(null); + jbpmEngine = (JBPMEngine)factory.getFactory().getBean("jbpm_engine"); + if (jbpmEngine == null) + { + throw new WorkflowException("Failed to retrieve JBPMEngine component"); + } + } + return jbpmEngine; + } + + /** + * Construct + */ + public WorkflowTaskInstance() + { + super(); + } + + /** + * Construct + * + * @param taskName + * @param actorId + */ + public WorkflowTaskInstance(String taskName, String actorId) + { + super(taskName, actorId); + } + + /** + * Construct + * + * @param taskName + */ + public WorkflowTaskInstance(String taskName) + { + super(taskName); + } + + @Override + public void create(ExecutionContext executionContext) + { + super.create(executionContext); + getJBPMEngine().setDefaultTaskProperties(this); + } + + @Override + public void end(Transition transition) + { + // NOTE: Set the outcome first, so it's available during the submission of + // task variables to the process context + Transition outcome = (transition == null) ? token.getNode().getDefaultLeavingTransition() : transition; + if (outcome != null) + { + getJBPMEngine().setTaskOutcome(this, outcome); + } + super.end(transition); + } + +} diff --git a/source/java/org/alfresco/repo/workflow/jbpm/WorkflowTaskInstanceFactory.java b/source/java/org/alfresco/repo/workflow/jbpm/WorkflowTaskInstanceFactory.java new file mode 100644 index 0000000000..967956828e --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/jbpm/WorkflowTaskInstanceFactory.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.workflow.jbpm; + +import org.jbpm.graph.exe.ExecutionContext; +import org.jbpm.taskmgmt.TaskInstanceFactory; +import org.jbpm.taskmgmt.exe.TaskInstance; + + +/** + * jBPM factory for creating Alfresco derived Task Instances + * + * @author davidc + */ +public class WorkflowTaskInstanceFactory implements TaskInstanceFactory +{ + private static final long serialVersionUID = -8097108150047415711L; + + + /* (non-Javadoc) + * @see org.jbpm.taskmgmt.TaskInstanceFactory#createTaskInstance(org.jbpm.graph.exe.ExecutionContext) + */ + public TaskInstance createTaskInstance(ExecutionContext executionContext) + { + return new WorkflowTaskInstance(); + } +} diff --git a/source/java/org/alfresco/repo/workflow/jbpm/adhoc_task_processdefinition.xml b/source/java/org/alfresco/repo/workflow/jbpm/adhoc_task_processdefinition.xml new file mode 100644 index 0000000000..013fdb6115 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/jbpm/adhoc_task_processdefinition.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/java/org/alfresco/repo/workflow/jbpm/jbpm.cfg.xml b/source/java/org/alfresco/repo/workflow/jbpm/jbpm.cfg.xml new file mode 100644 index 0000000000..e15d0b62a3 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/jbpm/jbpm.cfg.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/source/java/org/alfresco/repo/workflow/jbpm/jbpm.converter.properties b/source/java/org/alfresco/repo/workflow/jbpm/jbpm.converter.properties new file mode 100644 index 0000000000..0201847cf7 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/jbpm/jbpm.converter.properties @@ -0,0 +1,19 @@ +# this file contains the mappings between converter types +# and the char that is used in the database. this mapping +# is used by the ConverterEnumType to store the VariableInstance +# converter field. The Converters class provides singleton access +# to these converter classes. + +B org.jbpm.context.exe.converter.BooleanToStringConverter +Y org.jbpm.context.exe.converter.BytesToByteArrayConverter +E org.jbpm.context.exe.converter.ByteToLongConverter +C org.jbpm.context.exe.converter.CharacterToStringConverter +A org.jbpm.context.exe.converter.DateToLongConverter +D org.jbpm.context.exe.converter.DoubleToStringConverter +F org.jbpm.context.exe.converter.FloatToStringConverter +G org.jbpm.context.exe.converter.FloatToDoubleConverter +I org.jbpm.context.exe.converter.IntegerToLongConverter +R org.jbpm.context.exe.converter.SerializableToByteArrayConverter +H org.jbpm.context.exe.converter.ShortToLongConverter +N org.alfresco.repo.workflow.jbpm.NodeConverter +L org.alfresco.repo.workflow.jbpm.NodeListConverter \ No newline at end of file diff --git a/source/java/org/alfresco/repo/workflow/jbpm/jbpm.varmapping.xml b/source/java/org/alfresco/repo/workflow/jbpm/jbpm.varmapping.xml new file mode 100644 index 0000000000..77e7f26a0d --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/jbpm/jbpm.varmapping.xml @@ -0,0 +1,190 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/source/java/org/alfresco/repo/workflow/jbpm/review_and_approve_processdefinition.xml b/source/java/org/alfresco/repo/workflow/jbpm/review_and_approve_processdefinition.xml new file mode 100644 index 0000000000..db8f6309be --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/jbpm/review_and_approve_processdefinition.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/java/org/alfresco/repo/workflow/jbpm/test-messages.properties b/source/java/org/alfresco/repo/workflow/jbpm/test-messages.properties new file mode 100644 index 0000000000..884a741279 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/jbpm/test-messages.properties @@ -0,0 +1,14 @@ +test.workflow.title=Test +test.workflow.description=Workflow for testing purposes + +test.node.start.title=Start +test.node.start.description=The Start +test.node.review.title=Review +test.node.review.description=The Review +test.node.end.title=End +test.node.end.description=The End + +test.node.start.transition.review=Review + +test.task.submit.title=Submit Review Title +test.task.submit.description=Submit Review Description \ No newline at end of file diff --git a/source/java/org/alfresco/repo/workflow/jbpm/test_processdefinition.xml b/source/java/org/alfresco/repo/workflow/jbpm/test_processdefinition.xml new file mode 100644 index 0000000000..f05428d4e6 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/jbpm/test_processdefinition.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/java/org/alfresco/repo/workflow/jbpm/test_script.xml b/source/java/org/alfresco/repo/workflow/jbpm/test_script.xml new file mode 100644 index 0000000000..907e9558e8 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/jbpm/test_script.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/source/java/org/alfresco/service/ServiceRegistry.java b/source/java/org/alfresco/service/ServiceRegistry.java index 3d26bcbf29..3fc80541da 100644 --- a/source/java/org/alfresco/service/ServiceRegistry.java +++ b/source/java/org/alfresco/service/ServiceRegistry.java @@ -38,6 +38,7 @@ import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.cmr.version.VersionService; import org.alfresco.service.cmr.view.ExporterService; import org.alfresco.service.cmr.view.ImporterService; +import org.alfresco.service.cmr.workflow.WorkflowService; import org.alfresco.service.descriptor.DescriptorService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; @@ -81,6 +82,7 @@ public interface ServiceRegistry static final QName TEMPLATE_SERVICE = QName.createQName(NamespaceService.ALFRESCO_URI, "TemplateService"); static final QName FILE_FOLDER_SERVICE = QName.createQName(NamespaceService.ALFRESCO_URI, "FileFolderService"); static final QName SCRIPT_SERVICE = QName.createQName(NamespaceService.ALFRESCO_URI, "ScriptService"); + static final QName WORKFLOW_SERVICE = QName.createQName(NamespaceService.ALFRESCO_URI, "WorkflowService"); /** * Get the list of services provided by the Repository @@ -254,4 +256,10 @@ public interface ServiceRegistry */ @NotAuditable ScriptService getScriptService(); + + /** + * @return the workflow service (or null if one is not provided) + */ + @NotAuditable + WorkflowService getWorkflowService(); } diff --git a/source/java/org/alfresco/service/cmr/action/Action.java b/source/java/org/alfresco/service/cmr/action/Action.java index d926a86bca..cca64b0ee4 100644 --- a/source/java/org/alfresco/service/cmr/action/Action.java +++ b/source/java/org/alfresco/service/cmr/action/Action.java @@ -29,6 +29,14 @@ import org.alfresco.service.cmr.repository.NodeRef; */ public interface Action extends ParameterizedItem { + /** + * Gets the node ref that represents the saved action node. + * Returns null id unsaved. + * + * @return the action node reference + */ + NodeRef getNodeRef(); + /** * Get the name of the action definition that relates to this action * @@ -63,16 +71,6 @@ public interface Action extends ParameterizedItem * @param description the description of the action */ void setDescription(String description); - - /** - * Get the node reference of the node that 'owns' this action. - *

- * The node that 'owns' the action is th one that stores it via its - * actionable aspect association. - * - * @return node reference - */ - NodeRef getOwningNodeRef(); /** * Gets a value indicating whether the action should be executed asychronously or not. diff --git a/source/java/org/alfresco/service/cmr/action/ParameterDefinition.java b/source/java/org/alfresco/service/cmr/action/ParameterDefinition.java index 53e822b170..be67da8e3b 100644 --- a/source/java/org/alfresco/service/cmr/action/ParameterDefinition.java +++ b/source/java/org/alfresco/service/cmr/action/ParameterDefinition.java @@ -41,6 +41,11 @@ public interface ParameterDefinition */ public QName getType(); + /** + * Is multi-valued? + */ + public boolean isMultiValued(); + /** * Indicates whether the parameter is mandatory or not. *

diff --git a/source/java/org/alfresco/service/cmr/model/FileExistsException.java b/source/java/org/alfresco/service/cmr/model/FileExistsException.java index 23310a9f8f..1dfa85fab4 100644 --- a/source/java/org/alfresco/service/cmr/model/FileExistsException.java +++ b/source/java/org/alfresco/service/cmr/model/FileExistsException.java @@ -16,29 +16,37 @@ */ package org.alfresco.service.cmr.model; +import org.alfresco.service.cmr.repository.NodeRef; + /** * Common, checked exception thrown when an operation fails because * of a name clash. * * @author Derek Hulley */ -public class FileExistsException extends Exception +public class FileExistsException extends RuntimeException { private static final long serialVersionUID = -4133713912784624118L; - private FileInfo existing; + private NodeRef parentNodeRef; + private String name; - public FileExistsException(FileInfo existing) + public FileExistsException(NodeRef parentNodeRef, String name) { - super("" + - (existing.isFolder() ? "Folder " : "File ") + - existing.getName() + + super("Existing file or folder " + + name + " already exists"); - this.existing = existing; + this.parentNodeRef = parentNodeRef; + this.name = name; } - public FileInfo getExisting() + public NodeRef getParentNodeRef() { - return existing; + return parentNodeRef; + } + + public String getName() + { + return name; } } diff --git a/source/java/org/alfresco/service/cmr/model/FileFolderService.java b/source/java/org/alfresco/service/cmr/model/FileFolderService.java index 259e756622..2b939f4fe2 100644 --- a/source/java/org/alfresco/service/cmr/model/FileFolderService.java +++ b/source/java/org/alfresco/service/cmr/model/FileFolderService.java @@ -61,6 +61,16 @@ public interface FileFolderService @Auditable(key = Auditable.Key.ARG_0, parameters = {"contextNodeRef"}) public List listFolders(NodeRef contextNodeRef); + /** + * Get a simple list of nodes that have the given name within the parent node + * + * @param contextNodeRef the parent node + * @param name the name of the node to search for + * @return Returns the node that has the given name - or null if not found + */ + @Auditable(key = Auditable.Key.ARG_0, parameters = {"contextNodeRef", "name"}) + public NodeRef searchSimple(NodeRef contextNodeRef, String name); + /** * Searches for all files and folders with the matching name pattern, * using wildcard characters * and ?. diff --git a/source/java/org/alfresco/service/cmr/repository/AssociationExistsException.java b/source/java/org/alfresco/service/cmr/repository/AssociationExistsException.java index 9ccd21d69f..003e253a29 100644 --- a/source/java/org/alfresco/service/cmr/repository/AssociationExistsException.java +++ b/source/java/org/alfresco/service/cmr/repository/AssociationExistsException.java @@ -33,9 +33,7 @@ public class AssociationExistsException extends RuntimeException private QName qname; /** - * @param sourceRef the source of the association - * @param targetRef the target of the association - * @param qname the qualified name of the association + * @see #AssociationExistsException(NodeRef, NodeRef, QName, Throwable) */ public AssociationExistsException(NodeRef sourceRef, NodeRef targetRef, QName qname) { @@ -45,6 +43,20 @@ public class AssociationExistsException extends RuntimeException this.qname = qname; } + /** + * @param sourceRef the source of the association + * @param targetRef the target of the association + * @param qname the qualified name of the association + * @param cause a causal exception + */ + public AssociationExistsException(NodeRef sourceRef, NodeRef targetRef, QName qname, Throwable cause) + { + super(cause); + this.sourceRef = sourceRef; + this.targetRef = targetRef; + this.qname = qname; + } + public NodeRef getSourceRef() { return sourceRef; diff --git a/source/java/org/alfresco/service/cmr/repository/ContentAccessor.java b/source/java/org/alfresco/service/cmr/repository/ContentAccessor.java index 6d29a28f75..2100e13bd8 100644 --- a/source/java/org/alfresco/service/cmr/repository/ContentAccessor.java +++ b/source/java/org/alfresco/service/cmr/repository/ContentAccessor.java @@ -25,6 +25,13 @@ import org.alfresco.service.transaction.TransactionService; */ public interface ContentAccessor { + /** + * Gets the open/close state of the underlying IO Channel. + * + * @return Returns true if the underlying IO Channel is open + */ + public boolean isChannelOpen(); + /** * Use this method to register any interest in events against underlying * content streams. diff --git a/source/java/org/alfresco/service/cmr/repository/ContentService.java b/source/java/org/alfresco/service/cmr/repository/ContentService.java index cbca110d5e..c1c032a6ab 100644 --- a/source/java/org/alfresco/service/cmr/repository/ContentService.java +++ b/source/java/org/alfresco/service/cmr/repository/ContentService.java @@ -126,6 +126,14 @@ public interface ContentService @Auditable(parameters = {"sourceMimetype", "targetMimetype"}) public ContentTransformer getTransformer(String sourceMimetype, String targetMimetype); + /** + * Fetch the transformer that is capable of transforming image content. + * + * @return Returns a transformer that can be used, or null if one was not available + */ + @Auditable + public ContentTransformer getImageTransformer(); + /** * Returns whether a transformer exists that can read the content from * the reader and write the content back out to the writer. diff --git a/source/java/org/alfresco/service/cmr/repository/DuplicateChildNodeNameException.java b/source/java/org/alfresco/service/cmr/repository/DuplicateChildNodeNameException.java new file mode 100644 index 0000000000..405c42a434 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/repository/DuplicateChildNodeNameException.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.service.cmr.repository; + +import org.alfresco.i18n.I18NUtil; +import org.alfresco.service.namespace.QName; + + +/** + * Thrown when a child node cm:name property violates the data dictionary + * duplicate child association constraint. + * + * @author Derek Hulley + */ +public class DuplicateChildNodeNameException extends RuntimeException +{ + private static final long serialVersionUID = 5143099335847200453L; + + private static final String ERR_DUPLICATE_NAME = "system.err.duplicate_name"; + + private NodeRef parentNodeRef; + private QName assocTypeQName; + private String name; + + public DuplicateChildNodeNameException(NodeRef parentNodeRef, QName assocTypeQName, String name) + { + super(I18NUtil.getMessage(ERR_DUPLICATE_NAME, name)); + this.parentNodeRef = parentNodeRef; + this.assocTypeQName = assocTypeQName; + this.name = name; + } + + public NodeRef getParentNodeRef() + { + return parentNodeRef; + } + + public QName getAssocTypeQName() + { + return assocTypeQName; + } + + public String getName() + { + return name; + } +} diff --git a/source/java/org/alfresco/service/cmr/repository/NodeService.java b/source/java/org/alfresco/service/cmr/repository/NodeService.java index 88e178ed64..96b192cf52 100644 --- a/source/java/org/alfresco/service/cmr/repository/NodeService.java +++ b/source/java/org/alfresco/service/cmr/repository/NodeService.java @@ -417,6 +417,21 @@ public interface NodeService QNamePattern qnamePattern) throws InvalidNodeRefException; + /** + * Get the node with the given name within the context of the parent node. The name + * is case-insensitive as Alfresco has to support case-insensitive clients as standard. + * + * @param nodeRef the parent node - usuall a container + * @param assocTypeQName the type of the association + * @param childName the name of the node as per the property cm:name + * @return Returns the child node or null if not found + */ + @Auditable(key = Auditable.Key.ARG_0 ,parameters = {"nodeRef", "assocTypeQName", "childName"}) + public NodeRef getChildByName( + NodeRef nodeRef, + QName assocTypeQName, + String childName); + /** * Fetches the primary parent-child relationship. *

diff --git a/source/java/org/alfresco/service/cmr/repository/TemplateNode.java b/source/java/org/alfresco/service/cmr/repository/TemplateNode.java index c9b64e52a4..74560b1ffd 100644 --- a/source/java/org/alfresco/service/cmr/repository/TemplateNode.java +++ b/source/java/org/alfresco/service/cmr/repository/TemplateNode.java @@ -30,6 +30,7 @@ import org.alfresco.model.ContentModel; import org.alfresco.repo.security.permissions.AccessDeniedException; import org.alfresco.repo.template.LuceneSearchResultsMap; import org.alfresco.repo.template.NamePathResultsMap; +import org.alfresco.repo.template.NodeSearchResultsMap; import org.alfresco.repo.template.SavedSearchResultsMap; import org.alfresco.repo.template.XPathResultsMap; import org.alfresco.service.ServiceRegistry; @@ -96,6 +97,9 @@ public final class TemplateNode implements Serializable private ChildAssociationRef primaryParentAssoc = null; + // ------------------------------------------------------------------------------ + // Construction + /** * Constructor * @@ -123,6 +127,10 @@ public final class TemplateNode implements Serializable this.properties = new QNameMap(this.services.getNamespaceService()); } + + // ------------------------------------------------------------------------------ + // Node API + /** * @return The GUID for the node */ @@ -200,41 +208,6 @@ public final class TemplateNode implements Serializable return this.children; } - /** - * @return A map capable of returning the TemplateNode at the specified Path as a child of this node. - */ - public Map getChildByNamePath() - { - return new NamePathResultsMap(this, this.services); - } - - /** - * @return A map capable of returning a List of TemplateNode objects from an XPath query - * as children of this node. - */ - public Map getChildrenByXPath() - { - return new XPathResultsMap(this, this.services); - } - - /** - * @return A map capable of returning a List of TemplateNode objects from an NodeRef to a Saved Search - * object. The Saved Search is executed and the resulting nodes supplied as a sequence. - */ - public Map getChildrenBySavedSearch() - { - return new SavedSearchResultsMap(this, this.services); - } - - /** - * @return A map capable of returning a List of TemplateNode objects from an NodeRef to a Lucene search - * string. The Saved Search is executed and the resulting nodes supplied as a sequence. - */ - public Map getChildrenByLuceneSearch() - { - return new LuceneSearchResultsMap(this, this.services); - } - /** * @return The associations for this Node. As a Map of assoc name to a List of TemplateNodes. */ @@ -367,6 +340,142 @@ public final class TemplateNode implements Serializable } } + /** + * @return true if the node is currently locked + */ + public boolean getIsLocked() + { + boolean locked = false; + + if (getAspects().contains(ContentModel.ASPECT_LOCKABLE)) + { + LockStatus lockStatus = this.services.getLockService().getLockStatus(this.nodeRef); + if (lockStatus == LockStatus.LOCKED || lockStatus == LockStatus.LOCK_OWNER) + { + locked = true; + } + } + + return locked; + } + + /** + * @return the parent node + */ + public TemplateNode getParent() + { + if (parent == null) + { + NodeRef parentRef = this.services.getNodeService().getPrimaryParent(nodeRef).getParentRef(); + // handle root node (no parent!) + if (parentRef != null) + { + parent = new TemplateNode(parentRef, this.services, this.imageResolver); + } + } + + return parent; + } + + /** + * @return the primary parent association so we can access the association QName and association type QName. + */ + public ChildAssociationRef getPrimaryParentAssoc() + { + if (primaryParentAssoc == null) + { + primaryParentAssoc = this.services.getNodeService().getPrimaryParent(nodeRef); + } + return primaryParentAssoc; + } + + + // ------------------------------------------------------------------------------ + // Content API + + /** + * @return the content String for this node from the default content property + * (@see ContentModel.PROP_CONTENT) + */ + public String getContent() + { + ContentService contentService = this.services.getContentService(); + ContentReader reader = contentService.getReader(this.nodeRef, ContentModel.PROP_CONTENT); + return (reader != null && reader.exists()) ? reader.getContentString() : ""; + } + + /** + * @return For a content document, this method returns the URL to the content stream for + * the default content property (@see ContentModel.PROP_CONTENT) + *

+ * For a container node, this method return the URL to browse to the folder in the web-client + */ + public String getUrl() + { + if (getIsDocument() == true) + { + try + { + return MessageFormat.format(CONTENT_DEFAULT_URL, new Object[] { + nodeRef.getStoreRef().getProtocol(), + nodeRef.getStoreRef().getIdentifier(), + nodeRef.getId(), + StringUtils.replace(URLEncoder.encode(getName(), "UTF-8"), "+", "%20") } ); + } + catch (UnsupportedEncodingException err) + { + throw new TemplateException("Failed to encode content URL for node: " + nodeRef, err); + } + } + else + { + return MessageFormat.format(FOLDER_BROWSE_URL, new Object[] { + nodeRef.getStoreRef().getProtocol(), + nodeRef.getStoreRef().getIdentifier(), + nodeRef.getId() } ); + } + } + + /** + * @return The mimetype encoding for content attached to the node from the default content property + * (@see ContentModel.PROP_CONTENT) + */ + public String getMimetype() + { + if (mimetype == null) + { + TemplateContentData content = (TemplateContentData)this.getProperties().get(ContentModel.PROP_CONTENT); + if (content != null) + { + mimetype = content.getMimetype(); + } + } + + return mimetype; + } + + /** + * @return The size in bytes of the content attached to the node from the default content property + * (@see ContentModel.PROP_CONTENT) + */ + public long getSize() + { + if (size == null) + { + TemplateContentData content = (TemplateContentData)this.getProperties().get(ContentModel.PROP_CONTENT); + if (content != null) + { + size = content.getSize(); + } + } + + return size != null ? size.longValue() : 0L; + } + + + // ------------------------------------------------------------------------------ + // Node Helper API + /** * @return FreeMarker NodeModel for the XML content of this node, or null if no parsable XML found */ @@ -457,24 +566,9 @@ public final class TemplateNode implements Serializable } } - /** - * @return true if the node is currently locked - */ - public boolean getIsLocked() - { - boolean locked = false; - - if (getAspects().contains(ContentModel.ASPECT_LOCKABLE)) - { - LockStatus lockStatus = this.services.getLockService().getLockStatus(this.nodeRef); - if (lockStatus == LockStatus.LOCKED || lockStatus == LockStatus.LOCK_OWNER) - { - locked = true; - } - } - - return locked; - } + + // ------------------------------------------------------------------------------ + // Security API /** * @return List of permissions applied to this Node. @@ -510,114 +604,56 @@ public final class TemplateNode implements Serializable return this.services.getPermissionService().getInheritParentPermissions(this.nodeRef); } - /** - * @return the parent node - */ - public TemplateNode getParent() - { - if (parent == null) - { - NodeRef parentRef = this.services.getNodeService().getPrimaryParent(nodeRef).getParentRef(); - // handle root node (no parent!) - if (parentRef != null) - { - parent = new TemplateNode(parentRef, this.services, this.imageResolver); - } - } - return parent; + // ------------------------------------------------------------------------------ + // Search API + + /** + * @return A map capable of returning the TemplateNode at the specified Path as a child of this node. + */ + public Map getChildByNamePath() + { + return new NamePathResultsMap(this, this.services); } /** - * @return the primary parent association so we can access the association QName and association type QName. + * @return A map capable of returning a List of TemplateNode objects from an XPath query + * as children of this node. */ - public ChildAssociationRef getPrimaryParentAssoc() + public Map getChildrenByXPath() { - if (primaryParentAssoc == null) - { - primaryParentAssoc = this.services.getNodeService().getPrimaryParent(nodeRef); - } - return primaryParentAssoc; + return new XPathResultsMap(this, this.services); } /** - * @return the content String for this node from the default content property - * (@see ContentModel.PROP_CONTENT) + * @return A map capable of returning a List of TemplateNode objects from an NodeRef to a Saved Search + * object. The Saved Search is executed and the resulting nodes supplied as a sequence. */ - public String getContent() + public Map getChildrenBySavedSearch() { - ContentService contentService = this.services.getContentService(); - ContentReader reader = contentService.getReader(this.nodeRef, ContentModel.PROP_CONTENT); - return (reader != null && reader.exists()) ? reader.getContentString() : ""; + return new SavedSearchResultsMap(this, this.services); } /** - * @return For a content document, this method returns the URL to the content stream for - * the default content property (@see ContentModel.PROP_CONTENT) - *

- * For a container node, this method return the URL to browse to the folder in the web-client + * @return A map capable of returning a List of TemplateNode objects from an NodeRef to a Lucene search + * string. The Saved Search is executed and the resulting nodes supplied as a sequence. */ - public String getUrl() + public Map getChildrenByLuceneSearch() { - if (getIsDocument() == true) - { - try - { - return MessageFormat.format(CONTENT_DEFAULT_URL, new Object[] { - nodeRef.getStoreRef().getProtocol(), - nodeRef.getStoreRef().getIdentifier(), - nodeRef.getId(), - StringUtils.replace(URLEncoder.encode(getName(), "UTF-8"), "+", "%20") } ); - } - catch (UnsupportedEncodingException err) - { - throw new TemplateException("Failed to encode content URL for node: " + nodeRef, err); - } - } - else - { - return MessageFormat.format(FOLDER_BROWSE_URL, new Object[] { - nodeRef.getStoreRef().getProtocol(), - nodeRef.getStoreRef().getIdentifier(), - nodeRef.getId() } ); - } + return new LuceneSearchResultsMap(this, this.services); } /** - * @return The mimetype encoding for content attached to the node from the default content property - * (@see ContentModel.PROP_CONTENT) + * @return A map capable of returning a TemplateNode for a single specified NodeRef reference. */ - public String getMimetype() + public Map getNodeByReference() { - if (mimetype == null) - { - TemplateContentData content = (TemplateContentData)this.getProperties().get(ContentModel.PROP_CONTENT); - if (content != null) - { - mimetype = content.getMimetype(); - } - } - - return mimetype; + return new NodeSearchResultsMap(this, this.services); } - /** - * @return The size in bytes of the content attached to the node from the default content property - * (@see ContentModel.PROP_CONTENT) - */ - public long getSize() - { - if (size == null) - { - TemplateContentData content = (TemplateContentData)this.getProperties().get(ContentModel.PROP_CONTENT); - if (content != null) - { - size = content.getSize(); - } - } - - return size != null ? size.longValue() : 0L; - } + + // ------------------------------------------------------------------------------ + // Misc helpers /** * @return the image resolver instance used by this node @@ -645,6 +681,9 @@ public final class TemplateNode implements Serializable } + // ------------------------------------------------------------------------------ + // Inner classes + /** * Inner class wrapping and providing access to a ContentData property */ diff --git a/source/java/org/alfresco/service/cmr/rule/Rule.java b/source/java/org/alfresco/service/cmr/rule/Rule.java index f358856fa4..c0945facca 100644 --- a/source/java/org/alfresco/service/cmr/rule/Rule.java +++ b/source/java/org/alfresco/service/cmr/rule/Rule.java @@ -14,43 +14,290 @@ * language governing permissions and limitations under the * License. */ - package org.alfresco.service.cmr.rule; -import org.alfresco.service.cmr.action.CompositeAction; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.repository.NodeRef; /** - * Rule Interface + * Rule class. + *

+ * Encapsulates all the information about a rule. Can be creted or editied and + * then passed to the rule service to create/update a rule instance. * * @author Roy Wetherall */ -public interface Rule extends CompositeAction +public class Rule implements Serializable { - /** - * Indicates that the rule is applied to the children of the associated - * node, not just the node itself. - *

- * By default this will be set to false. - * - * @return true if the rule is applied to the children of the associated node, - * false otherwise - */ - boolean isAppliedToChildren(); - - /** - * Set whether the rule is applied to all children of the associated node - * rather than just the node itself. - * - * @param isAppliedToChildren true if the rule should be applied to the children, false - * otherwise - */ - void applyToChildren(boolean isAppliedToChildren); + /** + * Serial version UID + */ + private static final long serialVersionUID = 3544385898889097524L; /** - * Get the rule type name - * - * @return the rule type name + * The rule node reference */ - String getRuleTypeName(); - } \ No newline at end of file + private NodeRef nodeRef; + + /** + * The title of the rule + */ + private String title; + + /** + * The description of the rule + */ + private String description; + + /** + * The rule types + */ + private List ruleTypes; + + /** + * The associated action + */ + private Action action; + + /** + * Indicates whether the rule should execute the action asynchronously or not + */ + private boolean executeAsynchronously = false; + + /** Indicates wehther the rule is marked as disabled or not */ + private boolean ruleDisabled = false; + + /** + * Indicates whether the rule is applied to all the children of the associated node + * rather than just the node itself. + */ + private boolean isAppliedToChildren = false; + + /** + * Constructor + */ + public Rule() + { + } + + /** + * Constructor. + * + * @param nodeRef the rule node reference + */ + public Rule(NodeRef nodeRef) + { + this.nodeRef = nodeRef; + } + + /** + * Set the action + * + * @param action the action + */ + public void setAction(Action action) + { + this.action = action; + } + + /** + * Gets the action associatied with the rule + * + * @return the action + */ + public Action getAction() + { + return action; + } + + /** + * Set the node reference of the rule + * + * @param nodeRef the rule node reference + */ + public void setNodeRef(NodeRef nodeRef) + { + this.nodeRef = nodeRef; + } + + /** + * Get the node reference of the rule + * + * @return the rule node reference + */ + public NodeRef getNodeRef() + { + return nodeRef; + } + + /** + * Set the title of the rule + * + * @param title the title + */ + public void setTitle(String title) + { + this.title = title; + } + + /** + * Get the title of the rule + * + * @return the title + */ + public String getTitle() + { + return title; + } + + /** + * Set the description of the rule + * + * @param description the description + */ + public void setDescription(String description) + { + this.description = description; + } + + /** + * Get the description of the rule + * + * @return the description + */ + public String getDescription() + { + return description; + } + + /** + * Indicates wehther this rule should be applied to the children of + * the owning space. + * + * @return true if the rule is to be applied to children, false otherwise + */ + public boolean isAppliedToChildren() + { + return this.isAppliedToChildren; + } + + /** + * Sets the values that indicates whether this rule should be applied to the children + * of the owning space. + * + * @param isAppliedToChildren true if the rule is to be applied to children, false otherwise + */ + + public void applyToChildren(boolean isAppliedToChildren) + { + this.isAppliedToChildren = isAppliedToChildren; + } + + /** + * Helper method to set one rule type on the rule. + * + * @param ruleType the rule type + */ + public void setRuleType(String ruleType) + { + List ruleTypes = new ArrayList(1); + ruleTypes.add(ruleType); + this.ruleTypes = ruleTypes; + } + + /** + * Set the rules rule types. + * + * @param ruleTypes list of rule types + */ + public void setRuleTypes(List ruleTypes) + { + this.ruleTypes = ruleTypes; + } + + /** + * Get the rules rule types. + * + * @return a list of rule types + */ + public List getRuleTypes() + { + return ruleTypes; + } + + /** + * Sets the value that indicates whether this associated action should be executed + * asynchrously or not + * + * @param executeAsynchronously true to execute action async, false otherwise + */ + public void setExecuteAsynchronously(boolean executeAsynchronously) + { + this.executeAsynchronously = executeAsynchronously; + } + + /** + * Indicates whether the associated action should be executed async or not + * + * @return true to execute async, false otherwise + */ + public boolean getExecuteAsynchronously() + { + return this.executeAsynchronously; + } + + /** + * Indicates wehther this rule has been disabled or not + * + * @return true if the rule has been disabled, false otherwise + */ + public boolean getRuleDisabled() + { + return this.ruleDisabled; + } + + /** + * Set the value that indicates wehther this rule has been disabled or not + * + * @param ruleDisabled true id the rule has been disabled, false otherwise + */ + public void setRuleDisabled(boolean ruleDisabled) + { + this.ruleDisabled = ruleDisabled; + } + + /** + * Hash code implementation + */ + @Override + public int hashCode() + { + return this.nodeRef.hashCode(); + } + + /** + * Equals implementation + */ + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj instanceof Rule) + { + Rule that = (Rule) obj; + return (this.nodeRef.equals(that.nodeRef)); + } + else + { + return false; + } + } +} + diff --git a/source/java/org/alfresco/service/cmr/rule/RuleService.java b/source/java/org/alfresco/service/cmr/rule/RuleService.java index 6117440b98..f14c871ee0 100644 --- a/source/java/org/alfresco/service/cmr/rule/RuleService.java +++ b/source/java/org/alfresco/service/cmr/rule/RuleService.java @@ -19,6 +19,7 @@ package org.alfresco.service.cmr.rule; import java.util.List; import org.alfresco.service.Auditable; +import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.repository.NodeRef; /** @@ -45,6 +46,26 @@ public interface RuleService @Auditable(parameters = {"name"}) public RuleType getRuleType(String name); + /** + * Enable rules for the current thread + */ + @Auditable + public void enableRules(); + + /** + * Diable rules for the current thread + */ + @Auditable + public void disableRules(); + + /** + * Indicates whether rules are currently enabled or not + * + * @return true if rules are enabled, false otherwise + */ + @Auditable + public boolean isEnabled(); + /** * Indicates wether the rules for a given node are enabled or not. If the * rules are not enabled then they will not be executed. @@ -150,26 +171,13 @@ public interface RuleService public int countRules(NodeRef nodeRef); /** - * Get the rule given its id. + * Get the rule given its node reference * * @param nodeRef the node reference - * @param ruleId the rule id - * @return the rule corresponding ot the id + * @return the rule corresponding to the node reference */ - @Auditable(key = Auditable.Key.ARG_0, parameters = {"nodeRef", "ruleId"}) - public Rule getRule(NodeRef nodeRef, String ruleId); - - /** - * Helper method to create a new rule. - *

- * Call add rule once the details of the rule have been specified in order - * to associate the rule with a node reference. - * - * @param ruleTypeName the name of the rule type - * @return the created rule - */ - @Auditable(parameters = {"ruleTypeName"}) - public Rule createRule(String ruleTypeName); + @Auditable(key = Auditable.Key.ARG_0, parameters = {"nodeRef"}) + public Rule getRule(NodeRef nodeRef); /** * Saves the details of the rule to the specified node reference. @@ -198,4 +206,25 @@ public interface RuleService */ @Auditable(key = Auditable.Key.ARG_0, parameters = {"nodeRef"}) public void removeAllRules(NodeRef nodeRef); + + /** + * Returns the owning node reference for a rule. + * + * @param rule the rule + * @return the owning node reference + */ + @Auditable(key = Auditable.Key.ARG_0, parameters = {"rule"}) + public NodeRef getOwningNodeRef(Rule rule); + + /** + * Returns the owning node reference for an action. Returns null for an unsaved action or one that is not + * parented by a rule. + * + * NOTE: this method is temporary and will be removed in future versions. It should only be used with good reason. + * + * @param action the action + * @return the owning node reference + */ + @Auditable(key = Auditable.Key.ARG_0, parameters = {"action"}) + public NodeRef getOwningNodeRef(Action action); } diff --git a/source/java/org/alfresco/service/cmr/rule/RuleType.java b/source/java/org/alfresco/service/cmr/rule/RuleType.java index 10fcb50d70..03c1fe8ab6 100644 --- a/source/java/org/alfresco/service/cmr/rule/RuleType.java +++ b/source/java/org/alfresco/service/cmr/rule/RuleType.java @@ -52,8 +52,9 @@ public interface RuleType /** * Trigger the rules of the rule type for the node on the actioned upon node. * - * @param nodeRef the node ref whos rule of rule type are to be triggered - * @param actionedUponNodeRef the node ref that the triggered rule will action upon + * @param nodeRef the node ref whos rule of rule type are to be triggered + * @param actionedUponNodeRef the node ref that the triggered rule will action upon + * @prarm executeRuleImmediately indicates whether the rule should be executed immediately or not */ - public void triggerRuleType(NodeRef nodeRef, NodeRef actionedUponNodeRef); + public void triggerRuleType(NodeRef nodeRef, NodeRef actionedUponNodeRef, boolean executeRuleImmediately); } \ No newline at end of file diff --git a/source/java/org/alfresco/service/cmr/security/PersonService.java b/source/java/org/alfresco/service/cmr/security/PersonService.java index 0274a5414a..c985085742 100644 --- a/source/java/org/alfresco/service/cmr/security/PersonService.java +++ b/source/java/org/alfresco/service/cmr/security/PersonService.java @@ -40,12 +40,16 @@ public interface PersonService { /** * Get a person by userName. The person is store in the repository. The - * person may be created as a side effect of this call, depending on - * the setting to {@link #setCreateMissingPeople(boolean) create missing people or not}. + * person may be created as a side effect of this call, depending on the + * setting to + * {@link #setCreateMissingPeople(boolean) create missing people or not}. * - * @param userName - the userName key to find the person + * @param userName - + * the userName key to find the person * @return Returns the person node, either existing or new - * @throws NoSuchPersonException if the user doesn't exist and could not be created automatically + * @throws NoSuchPersonException + * if the user doesn't exist and could not be created + * automatically * * @see #setCreateMissingPeople(boolean) * @see #createMissingPeople() @@ -56,12 +60,13 @@ public interface PersonService /** * Check if a person exists. * - * @param userName the user name + * @param userName + * the user name * @return Returns true if the user exists, otherwise false */ @Auditable(parameters = {"userName"}) public boolean personExists(String userName); - + /** * Does this service create people on demand if they are missing. If this is * true, a call to getPerson() will create a person if they are missing. @@ -74,13 +79,14 @@ public interface PersonService /** * Set if missing people should be created. * - * @param createMissing set to true to create people + * @param createMissing + * set to true to create people * * @see #getPerson(String) */ @Auditable(parameters = {"createMissing"}) public void setCreateMissingPeople(boolean createMissing); - + /** * Get the list of properties that are mutable. Some service may only allow * a limited list of properties to be changed. This may be those persisted @@ -96,8 +102,10 @@ public interface PersonService * Set the properties on a person - some of these may be persisted in * different locations. * - * @param userName - the user for which the properties should be set. - * @param properties - the map of properties to set (as the NodeService) + * @param userName - + * the user for which the properties should be set. + * @param properties - + * the map of properties to set (as the NodeService) */ @Auditable(parameters = {"userName", "properties"}) public void setPersonProperties(String userName, Map properties); @@ -111,9 +119,8 @@ public interface PersonService public boolean isMutable(); /** - * Create a new person with the given properties. - * The userName is one of the properties. - * Users with duplicate userNames are not allowed. + * Create a new person with the given properties. The userName is one of the + * properties. Users with duplicate userNames are not allowed. * * @param properties * @return @@ -128,15 +135,15 @@ public interface PersonService */ @Auditable(parameters = {"userName"}) public void deletePerson(String userName); - + /** * Get all the people we know about. * - * @return a set of people in no specific order. + * @return a set of people in no specific order. */ @Auditable public Set getAllPeople(); - + /** * Return the container that stores people. * @@ -152,4 +159,17 @@ public interface PersonService */ @Auditable public boolean getUserNamesAreCaseSensitive(); + + /** + * Given the case sensitive user name find the approriate identifier from the person service. + * If the system is case sensitive it will return the same string. + * If case insentive it will return the common object. + * If the user does not exist it will return null; + * + * @param caseSensitiveUserName + * @return + */ + + public String getUserIdentifier(String caseSensitiveUserName); + } diff --git a/source/java/org/alfresco/service/cmr/workflow/WorkflowDefinition.java b/source/java/org/alfresco/service/cmr/workflow/WorkflowDefinition.java new file mode 100644 index 0000000000..2f709f2319 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/workflow/WorkflowDefinition.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.service.cmr.workflow; + + +/** + * Workflow Definition Data Object + * + * @author davidc + */ +public class WorkflowDefinition +{ + /** Workflow Definition unique id */ + public String id; + + /** Workflow Definition name */ + public String name; + + /** Workflow Definition version */ + public String version; + + /** Workflow Definition Title (Localised) */ + public String title; + + /** Workflow Definition Description (Localised) */ + public String description; + + /** Task Definition for Workflow Start Task (Optional) */ + public WorkflowTaskDefinition startTaskDefinition; + + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + public String toString() + { + return "WorkflowDefinition[id=" + id + ",version=" + version + ",title=" + title + ",startTask=" + startTaskDefinition.toString() + "]"; + } +} diff --git a/source/java/org/alfresco/service/cmr/workflow/WorkflowDeployment.java b/source/java/org/alfresco/service/cmr/workflow/WorkflowDeployment.java new file mode 100644 index 0000000000..f662896c26 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/workflow/WorkflowDeployment.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.service.cmr.workflow; + + +/** + * Workflow Definition Deployment + * + * @author davidc + */ +public class WorkflowDeployment +{ + /** Workflow Definition */ + public WorkflowDefinition definition; + + /** Workflow Status */ + public String[] problems; + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + public String toString() + { + return "WorkflowDeployment[def=" + definition + ",problems=" + ((problems == null) ? 0 : problems.length) + "]"; + } +} diff --git a/source/java/org/alfresco/service/cmr/workflow/WorkflowException.java b/source/java/org/alfresco/service/cmr/workflow/WorkflowException.java new file mode 100644 index 0000000000..e776ed6e18 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/workflow/WorkflowException.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.service.cmr.workflow; + +import org.alfresco.error.AlfrescoRuntimeException; + +/** + * Base Exception of Workflow Exceptions. + * + * @author David Caruana + */ +public class WorkflowException extends AlfrescoRuntimeException +{ + private static final long serialVersionUID = -7338963365877285084L; + + public WorkflowException(String msgId) + { + super(msgId); + } + + public WorkflowException(String msgId, Throwable cause) + { + super(msgId, cause); + } + + public WorkflowException(String msgId, Object ... args) + { + super(msgId, args); + } + + public WorkflowException(String msgId, Throwable cause, Object ... args) + { + super(msgId, args, cause); + } +} diff --git a/source/java/org/alfresco/service/cmr/workflow/WorkflowInstance.java b/source/java/org/alfresco/service/cmr/workflow/WorkflowInstance.java new file mode 100644 index 0000000000..6a92981346 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/workflow/WorkflowInstance.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.service.cmr.workflow; + +import java.util.Date; + +import org.alfresco.service.cmr.repository.NodeRef; + + +/** + * Workflow Instance Data Object + * + * Represents an "in-flight" workflow. + * + * @author davidc + */ +public class WorkflowInstance +{ + /** Workflow Instance unique id */ + public String id; + + /** Is this Workflow instance still "in-flight" or has it completed? */ + public boolean active; + + /** Initiator (cm:person) - null if System initiated */ + public NodeRef initiator; + + /** Workflow Start Date */ + public Date startDate; + + /** Workflow End Date */ + public Date endDate; + + /** Workflow Definition */ + public WorkflowDefinition definition; + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + public String toString() + { + return "WorkflowInstance[id=" + id + ",active=" + active + ",def=" + definition.toString() + "]"; + } +} diff --git a/source/java/org/alfresco/service/cmr/workflow/WorkflowNode.java b/source/java/org/alfresco/service/cmr/workflow/WorkflowNode.java new file mode 100644 index 0000000000..4d717a3965 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/workflow/WorkflowNode.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.service.cmr.workflow; + + +/** + * Workflow Node Data Object + * + * Represents a Node within the Workflow Definition. + * + * @author davidc + */ +public class WorkflowNode +{ + /** Workflow Node Name */ + public String name; + + /** Workflow Node Title (Localised) */ + public String title; + + /** Workflow Node Description (Localised) */ + public String description; + + /** Type of the Workflow Node (typically this is BPM engine specific - informational only */ + public String type; + + /** Does this Workflow Node represent human interaction? */ + public boolean isTaskNode; + + /** The transitions leaving this node (or null, if none) */ + public WorkflowTransition[] transitions; + + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + public String toString() + { + String transitionsArray = "{"; + for (int i = 0; i < transitions.length; i++) + { + transitionsArray += ((i == 0) ? "" : ",") + "'" + transitions[i] + "'"; + } + transitionsArray += "}"; + return "WorkflowNode[title=" + title + ",type=" + type + ",transitions=" + transitionsArray + "]"; + } +} diff --git a/source/java/org/alfresco/service/cmr/workflow/WorkflowPath.java b/source/java/org/alfresco/service/cmr/workflow/WorkflowPath.java new file mode 100644 index 0000000000..c187dfd3c9 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/workflow/WorkflowPath.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.service.cmr.workflow; + + +/** + * Workflow Path Data Object + * + * Represents a path within an "in-flight" workflow instance. + * + * Simple workflows consists of a single "root" path. Multiple paths occur when a workflow + * instance branches, therefore more than one concurrent path is taken. + * + * @author davidc + */ +public class WorkflowPath +{ + /** Unique id of Workflow Path */ + public String id; + + /** Workflow Instance this path is part of */ + public WorkflowInstance instance; + + /** The Workflow Node the path is at */ + public WorkflowNode node; + + /** Is the path still active? */ + public boolean active; + + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + public String toString() + { + return "WorkflowPath[id=" + id + ",instance=" + instance.toString() + ",active=" + active + ",node=" + node.toString()+ "]"; + } +} diff --git a/source/java/org/alfresco/service/cmr/workflow/WorkflowService.java b/source/java/org/alfresco/service/cmr/workflow/WorkflowService.java new file mode 100644 index 0000000000..fe46a1c143 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/workflow/WorkflowService.java @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.service.cmr.workflow; + +import java.io.InputStream; +import java.io.Serializable; +import java.util.List; +import java.util.Map; + +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; + + +/** + * Workflow Service. + * + * Client facing API for interacting with Alfresco Workflows and Tasks. + * + * @author davidc + */ +public interface WorkflowService +{ + // + // Workflow Definition Management + // + + /** + * Deploy a Workflow Definition to the Alfresco Repository + * + * @param engineId the bpm engine id + * @param workflowDefinition the workflow definition + * @param mimetype the mimetype of the workflow definition + * @return workflow deployment descriptor + */ + public WorkflowDeployment deployDefinition(String engineId, InputStream workflowDefinition, String mimetype); + + /** + * Deploy a Workflow Definition to the Alfresco Repository + * + * Note: The specified content object must be of type bpm:workflowdefinition. + * This type describes for which BPM engine the definition is appropriate. + * + * @param workflowDefinition the content object containing the definition + * @return workflow deployment descriptor + */ + public WorkflowDeployment deployDefinition(NodeRef workflowDefinition); + + /** + * Is the specified Workflow Definition already deployed? + * + * Note: the notion of "already deployed" may differ between bpm engines. For example, + * different versions of the same process may be considered equal. + * + * @param engineId the bpm engine id + * @param workflowDefinition the definition to check + * @param mimetype the mimetype of the definition + * @return true => already deployed + */ + public boolean isDefinitionDeployed(String engineId, InputStream workflowDefinition, String mimetype); + + /** + * Undeploy an exisiting Workflow Definition + * + * TODO: Determine behaviour when "in-flight" workflow instances exist + * + * @param workflowDefinitionId the id of the definition to undeploy + */ + public void undeployDefinition(String workflowDefinitionId); + + /** + * Gets all deployed Workflow Definitions + * + * @return the deployed workflow definitions + */ + public List getDefinitions(); + + /** + * Gets a Workflow Definition by unique Id + * + * @param workflowDefinitionId the workflow definition id + * @return the deployed workflow definition + */ + public WorkflowDefinition getDefinitionById(String workflowDefinitionId); + + /** + * Gets a Workflow Definition by unique name + * + * @param workflowName workflow name e.g. jbpm://review + * @return the deployed workflow definition + */ + public WorkflowDefinition getDefinitionByName(String workflowName); + + + // + // Workflow Instance Management + // + + + /** + * Start a Workflow Instance + * + * @param workflowDefinitionId the workflow definition id + * @param parameters the initial set of parameters used to populate the "Start Task" properties + * @return the initial workflow path + */ + public WorkflowPath startWorkflow(String workflowDefinitionId, Map parameters); + + /** + * Start a Workflow Instance from an existing "Start Task" template node held in the + * Repository. The node must be of the Type as described in the Workflow Definition. + * + * @param templateDefinition the node representing the Start Task properties + * @return the initial workflow path + */ + public WorkflowPath startWorkflowFromTemplate(NodeRef templateDefinition); + + /** + * Gets all "in-flight" workflow instances of the specified Workflow Definition + * + * @param workflowDefinitionId the workflow definition id + * @return the list of "in-fligth" workflow instances + */ + public List getActiveWorkflows(String workflowDefinitionId); + + /** + * Gets all Paths for the specified Workflow instance + * + * @param workflowId workflow instance id + * @return the list of workflow paths + */ + public List getWorkflowPaths(String workflowId); + + /** + * Cancel an "in-fligth" Workflow instance + * + * @param workflowId the workflow instance to cancel + * @return an updated representation of the workflow instance + */ + public WorkflowInstance cancelWorkflow(String workflowId); + + /** + * Signal the transition from one Workflow Node to another + * + * @param pathId the workflow path to signal on + * @param transition the transition to follow (or null, for the default transition) + * @return the updated workflow path + */ + public WorkflowPath signal(String pathId, String transitionId); + + /** + * Gets all Tasks associated with the specified path + * + * @param pathId the path id + * @return the list of associated tasks + */ + public List getTasksForWorkflowPath(String pathId); + + + // + // Task Management + // + + /** + * Gets a Task by unique Id + * + * @param taskId the task id + * @return the task + */ + public WorkflowTask getTaskById(String taskId); + + /** + * Gets all tasks assigned to the specified authority + * + * @param authority the authority + * @param state filter by specified workflow task state + * @return the list of assigned tasks + */ + public List getAssignedTasks(String authority, WorkflowTaskState state); + + /** + * Gets the pooled tasks available to the specified authority + * + * @param authority the authority + * @return the list of pooled tasks + */ + public List getPooledTasks(String authority); + + /** + * Update the Properties and Associations of a Task + * + * @param taskId the task id to update + * @param properties the map of properties to set on the task (or null, if none to set) + * @param add the map of items to associate with the task (or null, if none to add) + * @param remove the map of items to dis-associate with the task (or null, if none to remove) + * @return the update task + */ + public WorkflowTask updateTask(String taskId, Map properties, Map> add, Map> remove); + + /** + * End the Task (i.e. complete the task) + * + * @param taskId the task id to end + * @param transition the task transition to take on completion (or null, for the default transition) + * @return the updated task + */ + public WorkflowTask endTask(String taskId, String transitionId); + + /** + * Create a Workflow Package (a container of content to route through the Workflow). + * + * If an existing container is supplied, it's supplemented with the workflow package aspect. + * + * @param container (optional) a pre-created container (e.g. folder, versioned folder or layered folder) + * @return the workflow package + */ + public NodeRef createPackage(NodeRef container); + +} diff --git a/source/java/org/alfresco/service/cmr/workflow/WorkflowTask.java b/source/java/org/alfresco/service/cmr/workflow/WorkflowTask.java new file mode 100644 index 0000000000..4eaa2cf4d5 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/workflow/WorkflowTask.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.service.cmr.workflow; + +import java.io.Serializable; +import java.util.Map; + +import org.alfresco.service.namespace.QName; + + +/** + * Workflow Task Data Object + * + * Represents a human-oriented task within an "in-flight" workflow instance + * + * @author davidc + */ +public class WorkflowTask +{ + /** Unique id of Task */ + public String id; + + /** Task Name */ + public String name; + + /** Task Title (Localised) */ + public String title; + + /** Task Description (Localised) */ + public String description; + + /** Task State */ + public WorkflowTaskState state; + + /** Workflow path this Task is associated with */ + public WorkflowPath path; + + /** Task Definition */ + public WorkflowTaskDefinition definition; + + /** Task Properties as described by Task Definition */ + public Map properties; + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + public String toString() + { + String propCount = (properties == null) ? "null" : "" + properties.size(); + return "WorkflowTask[id=" + id + ",title=" + title + ",state=" + state + ",props=" + propCount + ",def=" + definition + ",path=" + path.toString() + "]"; + } +} diff --git a/source/java/org/alfresco/service/cmr/workflow/WorkflowTaskDefinition.java b/source/java/org/alfresco/service/cmr/workflow/WorkflowTaskDefinition.java new file mode 100644 index 0000000000..4339d7e2a5 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/workflow/WorkflowTaskDefinition.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.service.cmr.workflow; + +import org.alfresco.service.cmr.dictionary.TypeDefinition; + + +/** + * Workflow Task Definition Data Object. + * + * Represents meta-data for a Workflow Task. The meta-data is described in terms + * of the Alfresco Data Dictionary. + * + * @author davidc + */ +public class WorkflowTaskDefinition +{ + /** Unique id of Workflow Task Definition */ + public String id; + + /** Workflow Node this task created from */ + public WorkflowNode node; + + /** Task Metadata */ + public TypeDefinition metadata; + + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + public String toString() + { + return "WorkflowTaskDefinition[id=" + id + ",metadata=" + metadata + "]"; + } +} diff --git a/source/java/org/alfresco/service/cmr/workflow/WorkflowTaskState.java b/source/java/org/alfresco/service/cmr/workflow/WorkflowTaskState.java new file mode 100644 index 0000000000..89c6d259ec --- /dev/null +++ b/source/java/org/alfresco/service/cmr/workflow/WorkflowTaskState.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.service.cmr.workflow; + + +/** + * Workflow Task State + * + * Represents the high-level state of Workflow Task (in relation to "in-flight" + * workflow instance). + * + * A user-defined task state may be represented as Task Property (and described + * by the Alfresco Data Dictionary). + * + * @author davidc + */ +public enum WorkflowTaskState +{ + IN_PROGRESS, + COMPLETED; +} diff --git a/source/java/org/alfresco/service/cmr/workflow/WorkflowTransition.java b/source/java/org/alfresco/service/cmr/workflow/WorkflowTransition.java new file mode 100644 index 0000000000..8fc62f5541 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/workflow/WorkflowTransition.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.service.cmr.workflow; + + +/** + * Workflow Transition. + * + * @author davidc + */ +public class WorkflowTransition +{ + /** Transition Id */ + public String id; + + /** Transition Title (Localised) */ + public String title; + + /** Transition Description (Localised) */ + public String description; + + /** Is this the default transition */ + public boolean isDefault; + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + public String toString() + { + return "WorkflowTransition[id=" + id + ",title=" + title + "]"; + } +} diff --git a/source/java/org/alfresco/service/descriptor/Descriptor.java b/source/java/org/alfresco/service/descriptor/Descriptor.java index 254aaf18d2..61be552fb1 100644 --- a/source/java/org/alfresco/service/descriptor/Descriptor.java +++ b/source/java/org/alfresco/service/descriptor/Descriptor.java @@ -52,6 +52,13 @@ public interface Descriptor */ public String getVersionLabel(); + /** + * Gets the build number + * + * @return the build number i.e. build-1 + */ + public String getVersionBuild(); + /** * Gets the full version number * diff --git a/source/java/org/alfresco/service/namespace/NamespaceService.java b/source/java/org/alfresco/service/namespace/NamespaceService.java index 9ef59abf26..2d2cf70c32 100644 --- a/source/java/org/alfresco/service/namespace/NamespaceService.java +++ b/source/java/org/alfresco/service/namespace/NamespaceService.java @@ -66,6 +66,18 @@ public interface NamespaceService extends NamespacePrefixResolver /** Application Model Prefix */ public static final String APP_MODEL_PREFIX = "app"; + /** Business Process Model URI */ + public static final String BPM_MODEL_1_0_URI = "http://www.alfresco.org/model/bpm/1.0"; + + /** Business Process Model Prefix */ + public static final String BPM_MODEL_PREFIX = "bpm"; + + /** Workflow Model URI */ + public static final String WORKFLOW_MODEL_1_0_URI = "http://www.alfresco.org/model/workflow/1.0"; + + /** Workflow Model Prefix */ + public static final String WORKFLOW_MODEL_PREFIX = "wf"; + /** Alfresco View Namespace URI */ public static final String REPOSITORY_VIEW_1_0_URI = "http://www.alfresco.org/view/repository/1.0"; diff --git a/source/test-resources/filefolder/filefolder-test-import.xml b/source/test-resources/filefolder/filefolder-test-import.xml index 9d7609596f..90e2430380 100644 --- a/source/test-resources/filefolder/filefolder-test-import.xml +++ b/source/test-resources/filefolder/filefolder-test-import.xml @@ -43,15 +43,15 @@ L0- Folder C - + - DUPLICATE + CHECK_FILE contentUrl=classpath:quick/quick.txt|mimetype=text/plain|size=|encoding= - - - DUPLICATE - + + + CHECK_FOLDER +