diff --git a/config/alfresco/action-services-context.xml b/config/alfresco/action-services-context.xml index 053bf1a317..9f750e8a40 100644 --- a/config/alfresco/action-services-context.xml +++ b/config/alfresco/action-services-context.xml @@ -51,30 +51,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - @@ -98,20 +74,37 @@ - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/alfresco/authentication-services-context.xml b/config/alfresco/authentication-services-context.xml index 9a2dd30f0b..e4069ddc88 100644 --- a/config/alfresco/authentication-services-context.xml +++ b/config/alfresco/authentication-services-context.xml @@ -154,8 +154,11 @@ - + + + + 256 + @@ -293,10 +296,10 @@ - - - ${home.folder.creation.eager} - + + + + diff --git a/config/alfresco/bootstrap/alfrescoUserStore.xml b/config/alfresco/bootstrap/alfrescoUserStore.xml index a1e187c835..93a21a651b 100644 --- a/config/alfresco/bootstrap/alfrescoUserStore.xml +++ b/config/alfresco/bootstrap/alfrescoUserStore.xml @@ -11,6 +11,8 @@ ${alfresco_user_store.adminusername} ${alfresco_user_store.adminpassword} + ${alfresco_user_store.adminsalt} + ${alfresco_user_store.adminpassword2} true false false diff --git a/config/alfresco/bootstrap/categoriesEmptyRoot.xml b/config/alfresco/bootstrap/categoriesEmptyRoot.xml deleted file mode 100644 index b7bbabcf28..0000000000 --- a/config/alfresco/bootstrap/categoriesEmptyRoot.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - categories - - - General - - - - - - \ No newline at end of file diff --git a/config/alfresco/bootstrap/webScriptsNoSamples.xml b/config/alfresco/bootstrap/webScriptsNoSamples.xml deleted file mode 100644 index f9550b2d75..0000000000 --- a/config/alfresco/bootstrap/webScriptsNoSamples.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - ${webscripts.url_addressable_web_services} - space-icon-default - ${webscripts.url_addressable_web_services} - ${webscripts.web_scripts} - - - - - - - - - - - true - ${webscripts.what_are_web_scripts} - contentUrl=classpath:alfresco/bootstrap/webscripts/readme.html|mimetype=text/html|size=|encoding=UTF-8|locale=en_US_ - - - readme.html - - - - - - - \ No newline at end of file diff --git a/config/alfresco/cache-context.xml b/config/alfresco/cache-context.xml index 25d03b122a..ea4eca3048 100644 --- a/config/alfresco/cache-context.xml +++ b/config/alfresco/cache-context.xml @@ -13,11 +13,17 @@ - - + + + + + + + + + + + diff --git a/config/alfresco/content-services-context.xml b/config/alfresco/content-services-context.xml index e75c214c24..92ea231226 100644 --- a/config/alfresco/content-services-context.xml +++ b/config/alfresco/content-services-context.xml @@ -13,7 +13,7 @@ --> - + diff --git a/config/alfresco/core-services-context.xml b/config/alfresco/core-services-context.xml index 9c0da6bbcb..fcc02a3266 100644 --- a/config/alfresco/core-services-context.xml +++ b/config/alfresco/core-services-context.xml @@ -286,6 +286,9 @@ ${db.pool.evict.idle.min} + + ${db.pool.evict.num.tests} + ${db.pool.validate.borrow} diff --git a/config/alfresco/dao/dao-context.xml b/config/alfresco/dao/dao-context.xml index 881888fdc7..b3cc499ac4 100644 --- a/config/alfresco/dao/dao-context.xml +++ b/config/alfresco/dao/dao-context.xml @@ -173,6 +173,9 @@ --> + + + @@ -213,6 +216,7 @@ + diff --git a/config/alfresco/import-export-context.xml b/config/alfresco/import-export-context.xml index 9aa2f29eb8..df5a8a0c0b 100644 --- a/config/alfresco/import-export-context.xml +++ b/config/alfresco/import-export-context.xml @@ -306,6 +306,8 @@ ${alfresco_user_store.adminusername} ${alfresco_user_store.adminpassword} + ${alfresco_user_store.adminsalt} + ${alfresco_user_store.adminpassword2} ${alfresco_user_store.system_container.childname} ${alfresco_user_store.user_container.childname} @@ -538,10 +540,10 @@ alfresco/messages/bootstrap-spaces - /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.templates.email.childname} - alfresco/bootstrap/notification/workflow-email-notification.xml - alfresco/messages/bootstrap-spaces - + /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.templates.email.childname} + alfresco/bootstrap/notification/workflow-email-notification.xml + alfresco/messages/bootstrap-spaces + /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.templates.rss.childname} alfresco/templates/rss_templates.acp diff --git a/config/alfresco/messages/subscription-service.properties b/config/alfresco/messages/subscription-service.properties index fa480dc1ed..9aef32cab9 100644 --- a/config/alfresco/messages/subscription-service.properties +++ b/config/alfresco/messages/subscription-service.properties @@ -1,6 +1,6 @@ # Subscription service messages -subscription.notification.email.subject={0} is now following you +subscription.notification.email.subject={0} is now following you on Alfresco subscription_service.err.disabled=The subscription is disabled subscription_service.err.write-denied=No permissions to update diff --git a/config/alfresco/public-services-security-context.xml b/config/alfresco/public-services-security-context.xml index c3abd3c9d3..8ae53c972d 100644 --- a/config/alfresco/public-services-security-context.xml +++ b/config/alfresco/public-services-security-context.xml @@ -874,7 +874,7 @@ org.alfresco.service.cmr.security.PersonService.deletePerson=ACL_METHOD.ROLE_ADMINISTRATOR org.alfresco.service.cmr.security.PersonService.notifyPerson=ACL_METHOD.ROLE_ADMINISTRATOR org.alfresco.service.cmr.security.PersonService.getAllPeople=ACL_ALLOW - org.alfresco.service.cmr.security.PersonService.getPeople=ACL_ALLOW + org.alfresco.service.cmr.security.PersonService.getPeople=ACL_ALLOW,AFTER_ACL_NODE.sys:base.ReadProperties org.alfresco.service.cmr.security.PersonService.getPeopleFilteredByProperty=ACL_ALLOW org.alfresco.service.cmr.security.PersonService.getPeopleContainer=ACL_ALLOW org.alfresco.service.cmr.security.PersonService.getUserNamesAreCaseSensitive=ACL_ALLOW @@ -886,7 +886,7 @@ - + diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties index 5d1be6df72..4c571b0117 100644 --- a/config/alfresco/repository.properties +++ b/config/alfresco/repository.properties @@ -340,13 +340,25 @@ db.pool.statements.max=40 db.pool.min=0 db.pool.idle=-1 db.pool.wait.max=-1 + db.pool.validate.query= db.pool.evict.interval=-1 db.pool.evict.idle.min=1800000 +# +# note: for 'db.pool.evict.num.tests' see http://commons.apache.org/dbcp/configuration.html (numTestsPerEvictionRun) +# and also following extract from "org.apache.commons.pool.impl.GenericKeyedObjectPool" (1.5.5) +# +# * The number of objects to examine during each run of the idle object evictor thread (if any). +# * When a negative value is supplied, ceil({@link #getNumIdle})/abs({@link #getNumTestsPerEvictionRun}) +# * tests will be run. I.e., when the value is -n, roughly one nth of the +# * idle objects will be tested per run. +# +db.pool.evict.num.tests=-1 + +db.pool.evict.validate=false db.pool.validate.borrow=true db.pool.validate.return=false -db.pool.evict.validate=false -# + db.pool.abandoned.detect=false db.pool.abandoned.time=300 # @@ -387,6 +399,8 @@ alfresco_user_store.adminusername=admin # Initial password - editing this will not have any effect once the repository is installed alfresco_user_store.adminpassword=209c6174da490caeb422f3fa5a7ae634 +alfresco_user_store.adminsalt=ad3b938f-c1ad-4f2b-828b-6f3afd30ffdd +alfresco_user_store.adminpassword2=f378d5d7b947d5c26f478e21819e7ec3a6668c8149b050d086c64447bc40173b # note: default guest username - should not be changed after installation alfresco_user_store.guestusername=guest @@ -520,8 +534,10 @@ repo.remote.endpoint=/service # persisted. create.missing.people=${server.transaction.allow-writes} -# Create home folders as people are created (true) or create them lazily (false) +# Create home folders (unless disabled, see next property) as people are created (true) or create them lazily (false) home.folder.creation.eager=true +# Disable home folder creation - if true then home folders are not created (neither eagerly nor lazily) +home.folder.creation.disabled=false # Should we consider zero byte content to be the same as no content when firing # content update policies? Prevents 'premature' firing of inbound content rules @@ -618,6 +634,7 @@ system.thumbnail.retryPeriod=60 system.thumbnail.retryCount=2 system.thumbnail.quietPeriod=604800 system.thumbnail.quietPeriodRetriesEnabled=true +system.thumbnail.redeployStaticDefsOnStartup=true # Content Transformers content.transformer.failover=true @@ -944,6 +961,7 @@ system.quickshare.enabled=true # Cache configuration # cache.propertyValueCache.maxItems=10000 +cache.propertyUniqueContextSharedCache.maxItems=10000 cache.contentDataSharedCache.maxItems=130000 cache.immutableEntitySharedCache.maxItems=50000 cache.node.rootNodesSharedCache.maxItems=1000 diff --git a/config/alfresco/subsystems/ActivitiesFeed/default/activities-feed-context.xml b/config/alfresco/subsystems/ActivitiesFeed/default/activities-feed-context.xml index 76ebd87449..672d4bd19d 100644 --- a/config/alfresco/subsystems/ActivitiesFeed/default/activities-feed-context.xml +++ b/config/alfresco/subsystems/ActivitiesFeed/default/activities-feed-context.xml @@ -29,6 +29,7 @@ + @@ -145,7 +146,10 @@ - /app:company_home/app:dictionary/app:email_templates/cm:activities/cm:activities-email.ftl + ${activities.feed.notifier.emailTemplatePath} + + + ${activities.feed.notifier.emailTemplateLocationType} diff --git a/config/alfresco/subsystems/ActivitiesFeed/default/activities-jobs.properties b/config/alfresco/subsystems/ActivitiesFeed/default/activities-jobs.properties index f34f85713b..b57ea9836b 100644 --- a/config/alfresco/subsystems/ActivitiesFeed/default/activities-jobs.properties +++ b/config/alfresco/subsystems/ActivitiesFeed/default/activities-jobs.properties @@ -13,6 +13,9 @@ activities.feed.notifier.startDelayMins=0 activities.feed.notifier.repeatIntervalMins=1440 activities.feed.notifier.enabled=true +activities.feed.notifier.emailTemplatePath=/app:company_home/app:dictionary/app:email_templates/cm:activities/cm:activities-email.ftl +activities.feed.notifier.emailTemplateLocationType=xpath + # activities feed generator activities.feed.generator.startDelayMins=0 activities.feed.generator.repeatIntervalMillis=30000 diff --git a/config/alfresco/subsystems/Authentication/alfrescoNtlm/alfresco-authentication-context.xml b/config/alfresco/subsystems/Authentication/alfrescoNtlm/alfresco-authentication-context.xml index 5339358b4d..7fd770e7af 100644 --- a/config/alfresco/subsystems/Authentication/alfrescoNtlm/alfresco-authentication-context.xml +++ b/config/alfresco/subsystems/Authentication/alfrescoNtlm/alfresco-authentication-context.xml @@ -82,6 +82,7 @@ + diff --git a/config/alfresco/subsystems/Subscriptions/default/subscription-service-context.xml b/config/alfresco/subsystems/Subscriptions/default/subscription-service-context.xml index eb48196228..aa651736c6 100644 --- a/config/alfresco/subsystems/Subscriptions/default/subscription-service-context.xml +++ b/config/alfresco/subsystems/Subscriptions/default/subscription-service-context.xml @@ -19,6 +19,16 @@ + + + + ${subscriptions.following.emailTemplatePath} + + + ${subscriptions.following.emailTemplateLocationType} + + + @@ -31,6 +41,7 @@ + \ No newline at end of file diff --git a/config/alfresco/subsystems/Subscriptions/default/subscription-service.properties b/config/alfresco/subsystems/Subscriptions/default/subscription-service.properties index edcf341a5d..ac6d462089 100644 --- a/config/alfresco/subsystems/Subscriptions/default/subscription-service.properties +++ b/config/alfresco/subsystems/Subscriptions/default/subscription-service.properties @@ -1,2 +1,5 @@ # Enables the subscription service -subscriptions.enabled=true \ No newline at end of file +subscriptions.enabled=true + +subscriptions.following.emailTemplatePath=/app:company_home/app:dictionary/app:email_templates/app:following/cm:following-email.html.ftl +subscriptions.following.emailTemplateLocationType=xpath \ No newline at end of file diff --git a/config/alfresco/subsystems/email/OutboundSMTP/outboundSMTP-context.xml b/config/alfresco/subsystems/email/OutboundSMTP/outboundSMTP-context.xml index 782062d059..e6faa1b2f8 100755 --- a/config/alfresco/subsystems/email/OutboundSMTP/outboundSMTP-context.xml +++ b/config/alfresco/subsystems/email/OutboundSMTP/outboundSMTP-context.xml @@ -67,6 +67,9 @@ ${mail.header} + + ${mail.validate.addresses} + ${mail.from.default} @@ -88,6 +91,13 @@ ${mail.testmessage.text} + + + + + + + diff --git a/config/alfresco/subsystems/email/OutboundSMTP/outboundSMTP.properties b/config/alfresco/subsystems/email/OutboundSMTP/outboundSMTP.properties index 5ef2954780..a342659f4d 100755 --- a/config/alfresco/subsystems/email/OutboundSMTP/outboundSMTP.properties +++ b/config/alfresco/subsystems/email/OutboundSMTP/outboundSMTP.properties @@ -34,3 +34,11 @@ mail.testmessage.send=false mail.testmessage.to= mail.testmessage.subject=Outbound SMTP mail.testmessage.text=The Outbound SMTP email subsystem is working. + +# validate email addresses +mail.validate.addresses= true + +# NOTE: DO NOT remove this value - leave the value empty if you don't want to override +dev.email.recipient.address= +# NOTE: DO NOT remove this value - leave as false if you don't want to override (if true then emails will not be sent) +dev.email.not.sent=false \ No newline at end of file diff --git a/config/alfresco/templates/activities-email-templates/activities-email.ftl b/config/alfresco/templates/activities-email-templates/activities-email.ftl index 032103a54f..6830eddb7c 100644 --- a/config/alfresco/templates/activities-email-templates/activities-email.ftl +++ b/config/alfresco/templates/activities-email-templates/activities-email.ftl @@ -120,7 +120,7 @@ - + diff --git a/config/alfresco/templates/activities-email-templates/activities-email_de.ftl b/config/alfresco/templates/activities-email-templates/activities-email_de.ftl index bd39a19750..549a4b0bda 100644 --- a/config/alfresco/templates/activities-email-templates/activities-email_de.ftl +++ b/config/alfresco/templates/activities-email-templates/activities-email_de.ftl @@ -120,7 +120,7 @@ - + diff --git a/config/alfresco/templates/activities-email-templates/activities-email_es.ftl b/config/alfresco/templates/activities-email-templates/activities-email_es.ftl index 3fc1e3ce7f..a8ec544a3d 100644 --- a/config/alfresco/templates/activities-email-templates/activities-email_es.ftl +++ b/config/alfresco/templates/activities-email-templates/activities-email_es.ftl @@ -120,7 +120,7 @@ - + diff --git a/config/alfresco/templates/activities-email-templates/activities-email_fr.ftl b/config/alfresco/templates/activities-email-templates/activities-email_fr.ftl index c68d902592..c8ee76548c 100644 --- a/config/alfresco/templates/activities-email-templates/activities-email_fr.ftl +++ b/config/alfresco/templates/activities-email-templates/activities-email_fr.ftl @@ -120,7 +120,7 @@ - + diff --git a/config/alfresco/templates/activities-email-templates/activities-email_it.ftl b/config/alfresco/templates/activities-email-templates/activities-email_it.ftl index f2a56632aa..da5cd6647f 100644 --- a/config/alfresco/templates/activities-email-templates/activities-email_it.ftl +++ b/config/alfresco/templates/activities-email-templates/activities-email_it.ftl @@ -120,7 +120,7 @@ - + diff --git a/config/alfresco/templates/activities-email-templates/activities-email_ja.ftl b/config/alfresco/templates/activities-email-templates/activities-email_ja.ftl index 4c146a70a2..d45475e824 100644 --- a/config/alfresco/templates/activities-email-templates/activities-email_ja.ftl +++ b/config/alfresco/templates/activities-email-templates/activities-email_ja.ftl @@ -120,7 +120,7 @@ - + diff --git a/config/alfresco/templates/activities-email-templates/activities-email_nl.ftl b/config/alfresco/templates/activities-email-templates/activities-email_nl.ftl index b9bebaea66..f0403a4075 100644 --- a/config/alfresco/templates/activities-email-templates/activities-email_nl.ftl +++ b/config/alfresco/templates/activities-email-templates/activities-email_nl.ftl @@ -120,7 +120,7 @@ - + diff --git a/config/alfresco/thumbnail-service-context.xml b/config/alfresco/thumbnail-service-context.xml index 529b02cde5..222509d785 100644 --- a/config/alfresco/thumbnail-service-context.xml +++ b/config/alfresco/thumbnail-service-context.xml @@ -202,11 +202,14 @@ - + + + + diff --git a/config/alfresco/tx-cache-context.xml b/config/alfresco/tx-cache-context.xml index 1b3e3bedf9..9503f268d8 100644 --- a/config/alfresco/tx-cache-context.xml +++ b/config/alfresco/tx-cache-context.xml @@ -1,11 +1,10 @@ - - + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> + - + @@ -16,10 +15,25 @@ + - - - + + + + + + + + + org.alfresco.cache.propertyUniqueContextTransactionalCache + + + + + + + + diff --git a/config/alfresco/workflow/adhoc.bpmn20.xml b/config/alfresco/workflow/adhoc.bpmn20.xml index 3c385f4d1c..9697da3526 100644 --- a/config/alfresco/workflow/adhoc.bpmn20.xml +++ b/config/alfresco/workflow/adhoc.bpmn20.xml @@ -40,7 +40,7 @@ - Verify the arbitrary task was completed. + Verify the task was completed. diff --git a/config/alfresco/workflow/workflow-messages.properties b/config/alfresco/workflow/workflow-messages.properties index 64459d39cf..fdcc13f648 100644 --- a/config/alfresco/workflow/workflow-messages.properties +++ b/config/alfresco/workflow/workflow-messages.properties @@ -90,8 +90,8 @@ wf_parallelgroupreview.node.review.transition.approve.description=Approve # Activiti Adhoc Task Workflow # -activitiAdhoc.workflow.title=Adhoc Workflow -activitiAdhoc.workflow.description=Assign arbitrary task to colleague using Activiti workflow engine +activitiAdhoc.workflow.title=New Task +activitiAdhoc.workflow.description=Assign a new task to yourself or a colleague # # Activiti Review And Approve Workflow @@ -105,8 +105,8 @@ activitiReview.task.rejected.description=The document was reviewed and rejected. # Parallel Review Workflow # -activitiParallelReview.workflow.title=Parallel Review And Approve -activitiParallelReview.workflow.description=Parallel Review and approval of content using Activiti workflow engine +activitiParallelReview.workflow.title=Send Document(s) For Review +activitiParallelReview.workflow.description=Request document approval from one or more colleagues activitiParallelReview.task.approved.description=The document was reviewed and approved. activitiParallelReview.task.rejected.description=The document was reviewed and rejected. @@ -141,14 +141,23 @@ publishWebContent.workflow.description=Publishing of web content using Activiti # Adhoc Task Definitions -wf_workflowmodel.type.wf_submitAdhocTask.title=Start Adhoc Task +wf_workflowmodel.type.wf_submitAdhocTask.title=Task wf_workflowmodel.type.wf_submitAdhocTask.description=Allocate task to colleague wf_workflowmodel.property.wf_notifyMe.title=Notify Me wf_workflowmodel.property.wf_notifyMe.description=Notify me when task is complete -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 +wf_workflowmodel.type.wf_adhocTask.title=Task +wf_workflowmodel.type.wf_adhocTask.description=Task allocated by colleague +wf_workflowmodel.type.wf_completedAdhocTask.title=Task Completed +wf_workflowmodel.type.wf_completedAdhocTask.description=Task Completed + +activitiAdhoc.task.wf_submitAdhocTask.title=Task +activitiAdhoc.task.wf_submitAdhocTask.description=Allocate task to colleague +activitiAdhoc.property.wf_notifyMe.title=Notify Me +activitiAdhoc.property.wf_notifyMe.description=Notify me when task is complete +activitiAdhoc.task.wf_adhocTask.title=Task +activitiAdhoc.task.wf_adhocTask.description=Task allocated by colleague +activitiAdhoc.task.wf_completedAdhocTask.title=Task Completed +activitiAdhoc.task.wf_completedAdhocTask.description=Task Completed # Review And Approve Task Definitions @@ -169,8 +178,8 @@ wf_workflowmodel.property.wf_reviewOutcome.description=Approve or Reject the con # Parallel Review And Approve Task Definitions -wf_workflowmodel.type.wf_submitParallelReviewTask.title=Start Parallel Review -wf_workflowmodel.type.wf_submitParallelReviewTask.description=Submit documents for review and approval to a list of people +wf_workflowmodel.type.wf_submitParallelReviewTask.title=Send Document(s) For Review +wf_workflowmodel.type.wf_submitParallelReviewTask.description=Request document approval from one or more colleagues wf_workflowmodel.property.wf_requiredApprovePercent.title=Required Approval Percentage wf_workflowmodel.property.wf_requiredApprovePercent.description=Percentage of reviewers who must approve for approval wf_workflowmodel.type.wf_rejectedParallelTask.title=Rejected @@ -188,6 +197,27 @@ wf_workflowmodel.property.wf_actualPercent.description=Actual approval percentag wf_workflowmodel.property.wf_reviewOutcome.title=Review Outcome wf_workflowmodel.property.wf_reviewOutcome.description=Review Outcome +activitiParallelReview.task.wf_submitParallelReviewTask.title=Send Document(s) For Review +activitiParallelReview.task.wf_submitParallelReviewTask.description=Request document approval from one or more colleagues +activitiParallelReview.property.wf_requiredApprovePercent.title=Required Approval Percentage +activitiParallelReview.property.wf_requiredApprovePercent.description=Percentage of reviewers who must approve for approval +activitiParallelReview.task.wf_activitiReviewTask.title=Review +activitiParallelReview.task.wf_activitiReviewTask.description=Review Documents to Approve or Reject them +activitiParallelReview.task.wf_rejectedParallelTask.title=Document Rejected +activitiParallelReview.task.wf_rejectedParallelTask.description=Document(s) were rejected +activitiParallelReview.task.wf_approvedParallelTask.title=Document Approved +activitiParallelReview.task.wf_approvedParallelTask.description=Document(s) were approved +activitiParallelReview.property.wf_reviewerCount.title=Number of Reviewers +activitiParallelReview.property.wf_reviewerCount.description=Number of reviewers +activitiParallelReview.property.wf_requiredPercent.title=Required Approval Percentage +activitiParallelReview.property.wf_requiredPercent.description=Required Approval Percentage +activitiParallelReview.property.wf_approveCount.title=Reviewers Who Approved +activitiParallelReview.property.wf_approveCount.description=Reviewers who approved +activitiParallelReview.property.wf_actualPercent.title=Actual Approval Percentage +activitiParallelReview.property.wf_actualPercent.description=Actual approval percentage +activitiParallelReview.property.wf_reviewOutcome.title=Review Outcome +activitiParallelReview.property.wf_reviewOutcome.description=Review Outcome + # Pooled Review Task Definitions wf_workflowmodel.type.wf_submitGroupReviewTask.title=Start Group Review diff --git a/pom.xml b/pom.xml index 4244511a71..5130f64444 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ alfresco-parent org.alfresco - 4.2.d-SNAPSHOT + HEAD-CONV-SNAPSHOT ../../pom-experimental.xml @@ -559,7 +559,7 @@ edu.ucar netcdf - 4.2 + 4.2-min diff --git a/source/java/org/alfresco/cmis/dictionary/CMISAbstractDictionaryService.java b/source/java/org/alfresco/cmis/dictionary/CMISAbstractDictionaryService.java index 24bd8be62d..84b9016091 100644 --- a/source/java/org/alfresco/cmis/dictionary/CMISAbstractDictionaryService.java +++ b/source/java/org/alfresco/cmis/dictionary/CMISAbstractDictionaryService.java @@ -214,7 +214,12 @@ public abstract class CMISAbstractDictionaryService extends AbstractLifecycleBea } } - private DictionaryRegistry getRegistry() + protected DictionaryRegistry getRegistry() + { + return getRegistryImpl(); + } + + protected DictionaryRegistry getRegistryImpl() { DictionaryRegistry registry = null; diff --git a/source/java/org/alfresco/opencmis/AlfrescoCmisServiceFactory.java b/source/java/org/alfresco/opencmis/AlfrescoCmisServiceFactory.java index c31daa7f44..4babcda8db 100644 --- a/source/java/org/alfresco/opencmis/AlfrescoCmisServiceFactory.java +++ b/source/java/org/alfresco/opencmis/AlfrescoCmisServiceFactory.java @@ -129,7 +129,7 @@ public class AlfrescoCmisServiceFactory extends AbstractServiceFactory AuthenticationUtil.clearCurrentSecurityContext(); } - AlfrescoCmisService cmisServiceTarget = new AlfrescoCmisServiceImpl(connector); + AlfrescoCmisService cmisServiceTarget = getCmisServiceTarget(connector); // Wrap it ProxyFactory proxyFactory = new ProxyFactory(cmisServiceTarget); @@ -150,4 +150,9 @@ public class AlfrescoCmisServiceFactory extends AbstractServiceFactory return wrapperService; } + + protected AlfrescoCmisService getCmisServiceTarget(CMISConnector connector) + { + return new AlfrescoCmisServiceImpl(connector); + } } diff --git a/source/java/org/alfresco/opencmis/AlfrescoCmisServiceImpl.java b/source/java/org/alfresco/opencmis/AlfrescoCmisServiceImpl.java index 33c986ff74..e2d050f266 100644 --- a/source/java/org/alfresco/opencmis/AlfrescoCmisServiceImpl.java +++ b/source/java/org/alfresco/opencmis/AlfrescoCmisServiceImpl.java @@ -180,7 +180,7 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr { // create a session -> set a cookie // if the CMIS client supports cookies that might help in clustered environments - ((HttpServletRequest) context.get(CallContext.HTTP_SERVLET_REQUEST)).getSession(); + ((HttpServletRequest) getContext().get(CallContext.HTTP_SERVLET_REQUEST)).getSession(); } // Authenticate @@ -195,8 +195,8 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr if (AuthenticationUtil.getFullyAuthenticatedUser() == null) { // We have to go to the repo and authenticate - String user = context.getUsername(); - String password = context.getPassword(); + String user = getContext().getUsername(); + String password = getContext().getPassword(); Authorization auth = new Authorization(user, password); if (auth.isTicket()) { @@ -213,8 +213,8 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr // // TODO: How is the proxy user working. // // Until we know what it is meant to do, it's not available // String currentUser = connector.getAuthenticationService().getCurrentUserName(); -// String user = context.getUsername(); -// String password = context.getPassword(); +// String user = getContext().getUsername(); +// String password = getContext().getPassword(); // if (currentUser != null && currentUser.equals(connector.getProxyUser())) // { // if (user != null && user.length() > 0) @@ -236,7 +236,12 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr { this.context = context; } - + + protected CallContext getContext() + { + return context; + } + @Override public void close() { @@ -580,7 +585,7 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr ObjectData object = connector.createCMISObject(ni, child, filter, includeAllowableActions, includeRelationships, renditionFilter, false, false); - if (context.isObjectInfoRequired()) + if (getContext().isObjectInfoRequired()) { getObjectInfo(repositoryId, ni.getObjectId(), includeRelationships); } @@ -707,7 +712,7 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr object.setObject(connector.createCMISObject( ni, filter, includeAllowableActions, includeRelationships, renditionFilter, false, false)); - if (context.isObjectInfoRequired()) + if (getContext().isObjectInfoRequired()) { getObjectInfo(repositoryId, ni.getObjectId(), includeRelationships); } @@ -772,7 +777,7 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr ObjectData result = connector.createCMISObject( parentInfo, filter, false, IncludeRelationships.NONE, CMISConnector.RENDITION_NONE, false, false); - if (context.isObjectInfoRequired()) + if (getContext().isObjectInfoRequired()) { getObjectInfo( repositoryId, @@ -812,7 +817,7 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr ObjectData object = connector.createCMISObject( parentInfo, filter, includeAllowableActions, includeRelationships, renditionFilter, false, false); - if (context.isObjectInfoRequired()) + if (getContext().isObjectInfoRequired()) { getObjectInfo(repositoryId, object.getId(), includeRelationships); } @@ -839,7 +844,7 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr ObjectData object = connector.createCMISObject( parentInfo, filter, includeAllowableActions, includeRelationships, renditionFilter, false, false); - if (context.isObjectInfoRequired()) + if (getContext().isObjectInfoRequired()) { getObjectInfo(repositoryId, object.getId(), includeRelationships); } @@ -996,7 +1001,7 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr ni, filter, includeAllowableActions, includeRelationships, renditionFilter, false, false); - if (context.isObjectInfoRequired()) + if (getContext().isObjectInfoRequired()) { getObjectInfo(repositoryId, ni.getObjectId(), includeRelationships); } @@ -1068,7 +1073,7 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr throw new CmisRuntimeException("Creation failed!"); } - if (context.isObjectInfoRequired()) + if (getContext().isObjectInfoRequired()) { try { @@ -1496,7 +1501,7 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr objectId.setValue(connector.createObjectId(nodeRef)); - if (context.isObjectInfoRequired()) + if (getContext().isObjectInfoRequired()) { getObjectInfo(repositoryId, objectId.getValue(), "*", IncludeRelationships.NONE); } @@ -1697,7 +1702,7 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr info, filter, includeAllowableActions, includeRelationships, renditionFilter, includePolicyIds, includeAcl); - if (context.isObjectInfoRequired()) + if (getContext().isObjectInfoRequired()) { getObjectInfo(repositoryId, info.getObjectId(), includeRelationships); } @@ -1736,7 +1741,7 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr info, fileInfo, filter, includeAllowableActions, includeRelationships, renditionFilter, includePolicyIds, includeAcl); - if (context.isObjectInfoRequired()) + if (getContext().isObjectInfoRequired()) { getObjectInfo(repositoryId, info.getObjectId(), includeRelationships); } @@ -1758,7 +1763,7 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr // what kind of object is it? CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object"); - if (context.isObjectInfoRequired()) + if (getContext().isObjectInfoRequired()) { getObjectInfo(repositoryId, info.getObjectId(), IncludeRelationships.NONE); } @@ -1993,7 +1998,7 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr result.add(connector.createCMISObject(info, filter, includeAllowableActions, IncludeRelationships.NONE, CMISConnector.RENDITION_NONE, false, false)); - if (context.isObjectInfoRequired()) + if (getContext().isObjectInfoRequired()) { getObjectInfo(repositoryId, info.getObjectId(), IncludeRelationships.NONE); } @@ -2009,7 +2014,7 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr pwcInfo, filter, includeAllowableActions, IncludeRelationships.NONE, CMISConnector.RENDITION_NONE, false, false)); - if (context.isObjectInfoRequired()) + if (getContext().isObjectInfoRequired()) { getObjectInfo(repositoryId, pwcInfo.getObjectId(), IncludeRelationships.NONE); } @@ -2025,7 +2030,7 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr versionInfo, filter, includeAllowableActions, IncludeRelationships.NONE, CMISConnector.RENDITION_NONE, false, false)); - if (context.isObjectInfoRequired()) + if (getContext().isObjectInfoRequired()) { getObjectInfo(repositoryId, versionInfo.getObjectId(), IncludeRelationships.NONE); } @@ -2057,7 +2062,7 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr versionInfo, filter, includeAllowableActions, includeRelationships, renditionFilter, includePolicyIds, includeAcl); - if (context.isObjectInfoRequired()) + if (getContext().isObjectInfoRequired()) { getObjectInfo(repositoryId, info.getObjectId(), includeRelationships); } @@ -2642,7 +2647,7 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr // -------------------------------------------------------- - private void checkRepositoryId(String repositoryId) + protected void checkRepositoryId(String repositoryId) { if (!connector.getRepositoryId().equals(repositoryId)) { diff --git a/source/java/org/alfresco/repo/action/ActionImpl.java b/source/java/org/alfresco/repo/action/ActionImpl.java index 3508b8cafc..22d0343fc1 100644 --- a/source/java/org/alfresco/repo/action/ActionImpl.java +++ b/source/java/org/alfresco/repo/action/ActionImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -52,6 +52,7 @@ public class ActionImpl extends ParameterizedItemImpl implements Action private String modifier; private String actionDefinitionName; private String runAsUserName; + private String tenantId; private Set actionChain; private List actionConditions = new ArrayList(); @@ -137,6 +138,7 @@ public class ActionImpl extends ParameterizedItemImpl implements Action ActionImpl actionImpl = (ActionImpl) action; this.executionInstance = actionImpl.getExecutionInstance(); this.runAsUserName = actionImpl.getRunAsUser(); + this.tenantId = actionImpl.getTenantId(); this.actionChain = actionImpl.actionChain; } } @@ -356,11 +358,21 @@ public class ActionImpl extends ParameterizedItemImpl implements Action { return this.runAsUserName; } + + public String getTenantId() + { + return this.tenantId; + } public void setRunAsUser(String runAsUserName) { this.runAsUserName = runAsUserName; } + + public void setTenantId(String tenantId) + { + this.tenantId = tenantId; + } @Override public NodeRef getNodeRef() diff --git a/source/java/org/alfresco/repo/action/ActionServiceImpl.java b/source/java/org/alfresco/repo/action/ActionServiceImpl.java index b5dc17c2c9..0585d6a037 100644 --- a/source/java/org/alfresco/repo/action/ActionServiceImpl.java +++ b/source/java/org/alfresco/repo/action/ActionServiceImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -38,6 +38,7 @@ import org.alfresco.repo.copy.DefaultCopyBehaviourCallback; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; import org.alfresco.repo.security.authentication.AuthenticationContext; +import org.alfresco.repo.tenant.TenantUtil; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.ActionCondition; @@ -113,7 +114,7 @@ public class ActionServiceImpl implements ActionService, RuntimeActionService, A /** * The asynchronous action execution queues map of name, queue */ - private Map asynchronousActionExecutionQueues; + private Map asynchronousActionExecutionQueues = new HashMap(); /** * Action transaction listener @@ -216,12 +217,26 @@ public class ActionServiceImpl implements ActionService, RuntimeActionService, A * * @param asynchronousActionExecutionQueue the asynchronous action execution * queues + * @deprecated Rather than inject a Map, it is + * preferable to inject individual {@link AsynchronousActionExecutionQueue} instances + * during bean initialisation in a spring init-method. */ public void setAsynchronousActionExecutionQueues( Map asynchronousActionExecutionQueues) { this.asynchronousActionExecutionQueues = asynchronousActionExecutionQueues; } + + /** + * This method registers an {@link AsynchronousActionExecutionQueue} with the {@link ActionService}. + * @param key + * @param asyncExecQueue + * @since Thor Phase 2 Sprint 2 + */ + public void registerAsynchronousActionExecutionQueue(String key, AsynchronousActionExecutionQueue asyncExecQueue) + { + this.asynchronousActionExecutionQueues.put(key, asyncExecQueue); + } public void init() { @@ -777,8 +792,9 @@ public class ActionServiceImpl implements ActionService, RuntimeActionService, A Action compensatingAction = action.getCompensatingAction(); if (compensatingAction != null) { - // Set the current user + // Set the current user & tenant. These should be the same for the primary action and the compensating action. ((ActionImpl) compensatingAction).setRunAsUser(currentUserName); + ((ActionImpl) compensatingAction).setTenantId(((ActionImpl)action).getTenantId()); queueAction(compensatingAction, actionedUponNodeRef); } } @@ -1612,6 +1628,13 @@ public class ActionServiceImpl implements ActionService, RuntimeActionService, A } ((ActionImpl) action).setRunAsUser(this.authenticationContext.getCurrentUserName()); + // Set the tenant context to the current tenant + if (logger.isDebugEnabled() == true) + { + logger.debug("The current tenant is: " + TenantUtil.getCurrentDomain()); + } + ((ActionImpl) action).setTenantId(TenantUtil.getCurrentDomain()); + // Ensure that the transaction listener is bound to the transaction AlfrescoTransactionSupport.bindListener(this.transactionListener); diff --git a/source/java/org/alfresco/repo/action/AsynchronousActionExecutionQueueImpl.java b/source/java/org/alfresco/repo/action/AsynchronousActionExecutionQueueImpl.java index ec7f8e1d7c..6ed169e447 100644 --- a/source/java/org/alfresco/repo/action/AsynchronousActionExecutionQueueImpl.java +++ b/source/java/org/alfresco/repo/action/AsynchronousActionExecutionQueueImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -34,15 +34,14 @@ import org.alfresco.repo.policy.ClassPolicyDelegate; import org.alfresco.repo.policy.PolicyComponent; import org.alfresco.repo.rule.RuleServiceImpl; import org.alfresco.repo.security.authentication.AuthenticationContext; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.tenant.TenantUtil; +import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.repo.transaction.TransactionListenerAdapter; import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.ActionServiceException; 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; @@ -57,11 +56,13 @@ public class AsynchronousActionExecutionQueueImpl implements AsynchronousActionE private static Log logger = LogFactory.getLog(AsynchronousActionExecutionQueueImpl.class); /** Services */ + private ActionServiceImpl actionServiceImpl; private ThreadPoolExecutor threadPoolExecutor; private TransactionService transactionService; private PolicyComponent policyComponent; private Map actionFilters = new ConcurrentHashMap(); + private String id; /** * We keep a record of ongoing asynchronous actions (this includes those being executed and @@ -85,10 +86,29 @@ public class AsynchronousActionExecutionQueueImpl implements AsynchronousActionE */ public void init() { + // Register the execution queue with the ActionService + actionServiceImpl.registerAsynchronousActionExecutionQueue(id, this); + // Register the policies onAsyncActionExecuteDelegate = policyComponent.registerClassPolicy(OnAsyncActionExecute.class); } - + + /** + * @since Thor Phase 2 Sprint 2 + */ + public void setActionServiceImpl(ActionServiceImpl serviceImpl) + { + this.actionServiceImpl = serviceImpl; + } + + /** + * @since Thor Phase 2 Sprint 2 + */ + public void setId(String id) + { + this.id = id; + } + /** * Set the thread pool, which may be shared with other components, that will be used * to run the actions. @@ -377,11 +397,13 @@ public class AsynchronousActionExecutionQueueImpl implements AsynchronousActionE final String userName = ((ActionImpl)ActionExecutionWrapper.this.action).getRunAsUser(); if (userName == null) { - throw new ActionServiceException("Cannot execute action asynchronously since run as user is 'null'"); + throw new ActionServiceException("Cannot execute action asynchronously since run as user is 'null'"); } + // Get the tenant the action was submitted from + final String tenantId = ((ActionImpl)ActionExecutionWrapper.this.action).getTenantId(); // import the content - RunAsWork actionRunAs = new RunAsWork() + TenantRunAsWork actionRunAs = new TenantRunAsWork() { public Object doWork() throws Exception { @@ -414,7 +436,7 @@ public class AsynchronousActionExecutionQueueImpl implements AsynchronousActionE return transactionService.getRetryingTransactionHelper().doInTransaction(actionCallback); } }; - AuthenticationUtil.runAs(actionRunAs, userName); + TenantUtil.runAsUserTenant(actionRunAs, userName, tenantId); } catch (Throwable e) { diff --git a/source/java/org/alfresco/repo/action/executer/MailActionExecuter.java b/source/java/org/alfresco/repo/action/executer/MailActionExecuter.java index e078e2bb8e..7c24b1161d 100644 --- a/source/java/org/alfresco/repo/action/executer/MailActionExecuter.java +++ b/source/java/org/alfresco/repo/action/executer/MailActionExecuter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2012 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -155,6 +155,8 @@ public class MailActionExecuter extends ActionExecuterAbstractBase private String testMessageSubject = "Test message"; private String testMessageText = "This is a test message."; + private boolean validateAddresses = true; + /** * Test mode prevents email messages from being sent. * It is used when unit testing when we don't actually want to send out email messages. @@ -322,6 +324,22 @@ public class MailActionExecuter extends ActionExecuterAbstractBase return true; } + + /** + * This stores an email address which, if it is set, overrides ALL email recipients sent from + * this class. It is intended for dev/test usage only !! + */ + private String testModeRecipient; + + public void setTestModeRecipient(String testModeRecipient) + { + this.testModeRecipient = testModeRecipient; + } + + public void setValidateAddresses(boolean validateAddresses) + { + this.validateAddresses = validateAddresses; + } @Override @@ -600,15 +618,36 @@ public class MailActionExecuter extends ActionExecuterAbstractBase messageRef[0].setFrom(fromDefaultAddress); } - - - // set subject line messageRef[0].setSubject((String)ruleAction.getParameterValue(PARAM_SUBJECT)); + if ((testModeRecipient != null) && (testModeRecipient.length() > 0) && (! testModeRecipient.equals("${dev.email.recipient.address}"))) + { + // If we have an override for the email recipient, we'll send the email to that address instead. + // We'll prefix the subject with the original recipient, but leave the email message unchanged in every other way. + messageRef[0].setTo(testModeRecipient); + + String emailRecipient = (String)ruleAction.getParameterValue(PARAM_TO); + if (emailRecipient == null) + { + Object obj = ruleAction.getParameterValue(PARAM_TO_MANY); + if (obj != null) + { + emailRecipient = obj.toString(); + } + } + + String recipientPrefixedSubject = "(" + emailRecipient + ") " + (String)ruleAction.getParameterValue(PARAM_SUBJECT); + + messageRef[0].setSubject(recipientPrefixedSubject); + } + // See if an email template has been specified String text = null; - NodeRef templateRef = (NodeRef)ruleAction.getParameterValue(PARAM_TEMPLATE); + + // templateRef: either a nodeRef or classpath (see ClasspathRepoTemplateLoader) + Serializable ref = ruleAction.getParameterValue(PARAM_TEMPLATE); + String templateRef = (ref instanceof NodeRef ? ((NodeRef)ref).toString() : (String)ref); if (templateRef != null) { Map suppliedModel = null; @@ -630,7 +669,7 @@ public class MailActionExecuter extends ActionExecuterAbstractBase Map model = createEmailTemplateModel(actionedUponNodeRef, suppliedModel, fromPerson); // process the template against the model - text = templateService.processTemplate("freemarker", templateRef.toString(), model); + text = templateService.processTemplate("freemarker", templateRef, model); } // set the text body of the message @@ -740,7 +779,7 @@ public class MailActionExecuter extends ActionExecuterAbstractBase // Validate the email, allowing for local email addresses EmailValidator emailValidator = EmailValidator.getInstance(true); - if (emailValidator.isValid(address)) + if (!validateAddresses || emailValidator.isValid(address)) { result = true; } diff --git a/source/java/org/alfresco/repo/activities/ActivityServiceImpl.java b/source/java/org/alfresco/repo/activities/ActivityServiceImpl.java index e12e35c862..905f705d6e 100644 --- a/source/java/org/alfresco/repo/activities/ActivityServiceImpl.java +++ b/source/java/org/alfresco/repo/activities/ActivityServiceImpl.java @@ -295,9 +295,12 @@ public class ActivityServiceImpl implements ActivityService, InitializingBean + maxFeedItems); } + String currentTenantDomain = tenantService.getCurrentUserDomain(); + for (ActivityFeedEntity activityFeed : activityFeeds) { - if (actvityFilter != null && !actvityFilter.contains(activityFeed.getActivityType())) { + if (actvityFilter != null && !actvityFilter.contains(activityFeed.getActivityType())) + { if (logger.isTraceEnabled()) { logger.trace("Filtering " + activityFeed.toString() + " \n by the activity filter."); @@ -305,7 +308,8 @@ public class ActivityServiceImpl implements ActivityService, InitializingBean continue; } - if (userFilter != null && !userFilter.contains(activityFeed.getPostUserId())) { + if (userFilter != null && !userFilter.contains(activityFeed.getPostUserId())) + { if (logger.isTraceEnabled()) { logger.trace("Filtering " + activityFeed.toString() + " \n by the user filter."); @@ -313,6 +317,19 @@ public class ActivityServiceImpl implements ActivityService, InitializingBean continue; } + if (siteId == null) + { + // note: pending requirements for THOR-224, for now assume all activities are within context of site and filter by current tenant + if (! currentTenantDomain.equals(tenantService.getDomain(activityFeed.getSiteNetwork()))) + { + if (logger.isTraceEnabled()) + { + logger.trace("Filtering " + activityFeed.toString() + " \n by the site/tenant filter."); + } + continue; + } + } + // In order to prevent unnecessary 304 revalidations on user avatars in the activity stream the // activity posting user avatars will be retrieved and added to the activity feed. This will enable // avatars to be requested using the unique nodeRef which can be safely cached by the browser and diff --git a/source/java/org/alfresco/repo/activities/feed/AbstractUserNotifier.java b/source/java/org/alfresco/repo/activities/feed/AbstractUserNotifier.java index 200aa0cfc2..9797baa69e 100644 --- a/source/java/org/alfresco/repo/activities/feed/AbstractUserNotifier.java +++ b/source/java/org/alfresco/repo/activities/feed/AbstractUserNotifier.java @@ -96,7 +96,7 @@ public abstract class AbstractUserNotifier implements UserNotifier protected abstract boolean skipUser(NodeRef personNodeRef); protected abstract Long getFeedId(NodeRef personNodeRef); - protected abstract void notifyUser(NodeRef personNodeRef, String subjectText, Map model, NodeRef templateNodeRef); + protected abstract void notifyUser(NodeRef personNodeRef, String subjectText, Map model, String templateNodeRef); private void addSiteName(String siteId, Map siteNames) { @@ -129,7 +129,7 @@ public abstract class AbstractUserNotifier implements UserNotifier } public Pair notifyUser(final NodeRef personNodeRef, String subjectText, Map siteNames, - String shareUrl, int repeatIntervalMins, NodeRef templateNodeRef) + String shareUrl, int repeatIntervalMins, String templateNodeRef) { Map personProps = nodeService.getProperties(personNodeRef); diff --git a/source/java/org/alfresco/repo/activities/feed/EmailUserNotifier.java b/source/java/org/alfresco/repo/activities/feed/EmailUserNotifier.java index a5e77715f2..a0e8110140 100644 --- a/source/java/org/alfresco/repo/activities/feed/EmailUserNotifier.java +++ b/source/java/org/alfresco/repo/activities/feed/EmailUserNotifier.java @@ -1,3 +1,21 @@ +/* + * Copyright (C) 2005-2012 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ package org.alfresco.repo.activities.feed; import java.io.Serializable; @@ -142,7 +160,7 @@ public class EmailUserNotifier extends AbstractUserNotifier implements Initializ return emailFeedDBID; } - protected void notifyUser(NodeRef personNodeRef, String subjectText, Map model, NodeRef templateNodeRef) + protected void notifyUser(NodeRef personNodeRef, String subjectText, Map model, String templateNodeRef) { ParameterCheck.mandatory("personNodeRef", personNodeRef); diff --git a/source/java/org/alfresco/repo/activities/feed/ErrorProneUserNotifier.java b/source/java/org/alfresco/repo/activities/feed/ErrorProneUserNotifier.java index 7feb812d46..71c4241cf0 100644 --- a/source/java/org/alfresco/repo/activities/feed/ErrorProneUserNotifier.java +++ b/source/java/org/alfresco/repo/activities/feed/ErrorProneUserNotifier.java @@ -4,7 +4,6 @@ import java.io.Serializable; import java.util.Map; import org.alfresco.model.ContentModel; -import org.alfresco.repo.action.executer.MailActionExecuter; import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.ActionService; import org.alfresco.service.cmr.repository.NodeRef; @@ -66,7 +65,7 @@ public class ErrorProneUserNotifier extends AbstractUserNotifier @Override protected void notifyUser(NodeRef personNodeRef, String subjectText, - Map model, NodeRef templateNodeRef) + Map model, String templateNodeRef) { // super.notifyUser(personNodeRef, subjectText, model, templateNodeRef); diff --git a/source/java/org/alfresco/repo/activities/feed/FeedNotifierImpl.java b/source/java/org/alfresco/repo/activities/feed/FeedNotifierImpl.java index cf71098864..57c72685ed 100644 --- a/source/java/org/alfresco/repo/activities/feed/FeedNotifierImpl.java +++ b/source/java/org/alfresco/repo/activities/feed/FeedNotifierImpl.java @@ -36,6 +36,7 @@ import org.alfresco.repo.dictionary.RepositoryLocation; import org.alfresco.repo.lock.JobLockService; import org.alfresco.repo.lock.JobLockService.JobLockRefreshCallback; import org.alfresco.repo.lock.LockAcquisitionException; +import org.alfresco.repo.search.SearcherException; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; @@ -259,30 +260,53 @@ public class FeedNotifierImpl implements FeedNotifier, ApplicationContextAware this.feedEmailTemplateLocation = feedEmailTemplateLocation; } - private NodeRef getEmailTemplateRef() + protected String getEmailTemplateRef() { - StoreRef store = feedEmailTemplateLocation.getStoreRef(); - String xpath = feedEmailTemplateLocation.getPath(); + String locationType = feedEmailTemplateLocation.getQueryLanguage(); - if (! feedEmailTemplateLocation.getQueryLanguage().equals(SearchService.LANGUAGE_XPATH)) + if (locationType.equals(SearchService.LANGUAGE_XPATH)) { - logger.warn("Cannot find the activities email template - repository location query language is not 'xpath': "+feedEmailTemplateLocation.getQueryLanguage()); + StoreRef store = feedEmailTemplateLocation.getStoreRef(); + String xpath = feedEmailTemplateLocation.getPath(); + + try + { + if (! feedEmailTemplateLocation.getQueryLanguage().equals(SearchService.LANGUAGE_XPATH)) + { + logger.error("Cannot find the activities email template - repository location query language is not 'xpath': "+feedEmailTemplateLocation.getQueryLanguage()); + return null; + } + + List nodeRefs = searchService.selectNodes(nodeService.getRootNode(store), xpath, null, namespaceService, false); + if (nodeRefs.size() != 1) + { + logger.error("Cannot find the activities email template: "+xpath); + return null; + } + + return fileFolderService.getLocalizedSibling(nodeRefs.get(0)).toString(); + } + catch (SearcherException e) + { + logger.error("Cannot find the email template!", e); + } + return null; } - - List nodeRefs = searchService.selectNodes(nodeService.getRootNode(store), xpath, null, namespaceService, false); - if (nodeRefs.size() != 1) + else if (locationType.equals(RepositoryLocation.LANGUAGE_CLASSPATH)) { - logger.warn("Cannot find the activities email template: "+xpath); + return feedEmailTemplateLocation.getPath(); + } + else + { + logger.error("Unsupported location type: "+locationType); return null; } - - return fileFolderService.getLocalizedSibling(nodeRefs.get(0)); } private void executeInternal(final int repeatIntervalMins) { - final NodeRef emailTemplateRef = getEmailTemplateRef(); + final String emailTemplateRef = getEmailTemplateRef(); if (emailTemplateRef == null) { diff --git a/source/java/org/alfresco/repo/activities/feed/MockUserNotifier.java b/source/java/org/alfresco/repo/activities/feed/MockUserNotifier.java index 71d473eb9a..0d9437fa22 100644 --- a/source/java/org/alfresco/repo/activities/feed/MockUserNotifier.java +++ b/source/java/org/alfresco/repo/activities/feed/MockUserNotifier.java @@ -1,3 +1,21 @@ +/* + * Copyright (C) 2005-2012 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ package org.alfresco.repo.activities.feed; import java.io.Serializable; @@ -49,7 +67,7 @@ public class MockUserNotifier extends AbstractUserNotifier } @Override - protected void notifyUser(NodeRef personNodeRef, String subjectText, Map model, NodeRef templateNodeRef) + protected void notifyUser(NodeRef personNodeRef, String subjectText, Map model, String templateNodeRef) { String username = (String)nodeService.getProperty(personNodeRef, ContentModel.PROP_USERNAME); if(username.startsWith("user")) diff --git a/source/java/org/alfresco/repo/activities/feed/UserNotifier.java b/source/java/org/alfresco/repo/activities/feed/UserNotifier.java index 423d639c5f..c754b49bc2 100644 --- a/source/java/org/alfresco/repo/activities/feed/UserNotifier.java +++ b/source/java/org/alfresco/repo/activities/feed/UserNotifier.java @@ -1,3 +1,21 @@ +/* + * Copyright (C) 2005-2012 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ package org.alfresco.repo.activities.feed; import java.util.Map; @@ -13,5 +31,5 @@ import org.alfresco.util.Pair; public interface UserNotifier { public Pair notifyUser(final NodeRef personNodeRef, String subjectText, Map siteNames, - String shareUrl, int repeatIntervalMins, NodeRef templateNodeRef); + String shareUrl, int repeatIntervalMins, String templateNodeRef); } diff --git a/source/java/org/alfresco/repo/activities/feed/cleanup/FeedCleaner.java b/source/java/org/alfresco/repo/activities/feed/cleanup/FeedCleaner.java index 58ea68216b..77db299e16 100644 --- a/source/java/org/alfresco/repo/activities/feed/cleanup/FeedCleaner.java +++ b/source/java/org/alfresco/repo/activities/feed/cleanup/FeedCleaner.java @@ -19,9 +19,11 @@ package org.alfresco.repo.activities.feed.cleanup; import java.sql.SQLException; +import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import org.alfresco.model.ContentModel; @@ -35,6 +37,7 @@ import org.alfresco.repo.node.NodeServicePolicies.BeforeDeleteNodePolicy; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; import org.alfresco.repo.site.SiteModel; +import org.alfresco.repo.tenant.TenantService; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.TransactionListenerAdapter; @@ -70,6 +73,7 @@ public class FeedCleaner implements NodeServicePolicies.BeforeDeleteNodePolicy private JobLockService jobLockService; private NodeService nodeService; + private TenantService tenantService; private PolicyComponent policyComponent; private TransactionService transactionService; @@ -92,6 +96,11 @@ public class FeedCleaner implements NodeServicePolicies.BeforeDeleteNodePolicy this.nodeService = nodeService; } + public void setTenantService(TenantService tenantService) + { + this.tenantService = tenantService; + } + public void setPolicyComponent(PolicyComponent policyComponent) { this.policyComponent = policyComponent; @@ -424,7 +433,13 @@ public class FeedCleaner implements NodeServicePolicies.BeforeDeleteNodePolicy { String userId = (String)nodeService.getProperty(personNodeRef, ContentModel.PROP_USERNAME); - Set deletedUserIds = TransactionalResourceHelper.getSet(KEY_DELETED_USER_IDS); + Set deletedUserIds = (Set)AlfrescoTransactionSupport.getResource(KEY_DELETED_USER_IDS); + if (deletedUserIds == null) + { + deletedUserIds = Collections.newSetFromMap(new ConcurrentHashMap()); // Java 6 + AlfrescoTransactionSupport.bindResource(KEY_DELETED_USER_IDS, deletedUserIds); + } + deletedUserIds.add(userId); AlfrescoTransactionSupport.bindListener(deletePersonTransactionListener); @@ -434,7 +449,13 @@ public class FeedCleaner implements NodeServicePolicies.BeforeDeleteNodePolicy { String siteId = (String)nodeService.getProperty(siteNodeRef, ContentModel.PROP_NAME); - Set deletedSiteIds = TransactionalResourceHelper.getSet(KEY_DELETED_SITE_IDS); + Set deletedSiteIds = (Set)AlfrescoTransactionSupport.getResource(KEY_DELETED_SITE_IDS); + if (deletedSiteIds == null) + { + deletedSiteIds = Collections.newSetFromMap(new ConcurrentHashMap()); // Java 6 + AlfrescoTransactionSupport.bindResource(KEY_DELETED_SITE_IDS, deletedSiteIds); + } + deletedSiteIds.add(siteId); AlfrescoTransactionSupport.bindListener(deleteSiteTransactionListener); diff --git a/source/java/org/alfresco/repo/activities/feed/local/LocalFeedTaskProcessor.java b/source/java/org/alfresco/repo/activities/feed/local/LocalFeedTaskProcessor.java index 5b5520098b..e1721e4d36 100644 --- a/source/java/org/alfresco/repo/activities/feed/local/LocalFeedTaskProcessor.java +++ b/source/java/org/alfresco/repo/activities/feed/local/LocalFeedTaskProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2012 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -40,6 +40,7 @@ import org.alfresco.repo.domain.activities.FeedControlEntity; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.template.ClassPathRepoTemplateLoader; import org.alfresco.repo.tenant.TenantService; +import org.alfresco.repo.tenant.TenantUtil; import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; @@ -277,7 +278,7 @@ public class LocalFeedTaskProcessor extends FeedTaskProcessor implements Applica final String siteId = tenantService.getBaseName(siteIdIn, true); // optimise for non-remote implementation - override remote repo callback (to "List Site Memberships" web script) with embedded call - return AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork>() + return TenantUtil.runAsSystemTenant(new TenantUtil.TenantRunAsWork>() { public Set doWork() throws Exception { @@ -301,7 +302,7 @@ public class LocalFeedTaskProcessor extends FeedTaskProcessor implements Applica return members; } - }, tenantService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenantDomain)); + }, tenantDomain); } } @@ -372,13 +373,13 @@ public class LocalFeedTaskProcessor extends FeedTaskProcessor implements Applica String tenantDomain = (String)model.get(PostLookup.JSON_TENANT_DOMAIN); if (tenantDomain == null) { tenantDomain = TenantService.DEFAULT_DOMAIN; } - return AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() + return TenantUtil.runAsSystemTenant(new TenantUtil.TenantRunAsWork() { public Boolean doWork() throws Exception { return canReadImpl(connectedUser, nodeRef); } - }, tenantService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenantDomain)); + }, tenantDomain); } else { @@ -585,7 +586,7 @@ public class LocalFeedTaskProcessor extends FeedTaskProcessor implements Applica if (subscriptionService.isActive()) { - AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() + TenantUtil.runAsSystemTenant(new TenantUtil.TenantRunAsWork() { public Void doWork() throws Exception { @@ -598,7 +599,7 @@ public class LocalFeedTaskProcessor extends FeedTaskProcessor implements Applica return null; } - }, tenantService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenantDomain)); + }, tenantDomain); } return result; diff --git a/source/java/org/alfresco/repo/admin/patch/impl/UpdateWorkflowNotificationTemplatesPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/UpdateWorkflowNotificationTemplatesPatch.java index afef4fe681..c200419ddc 100644 --- a/source/java/org/alfresco/repo/admin/patch/impl/UpdateWorkflowNotificationTemplatesPatch.java +++ b/source/java/org/alfresco/repo/admin/patch/impl/UpdateWorkflowNotificationTemplatesPatch.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2011 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -54,7 +54,7 @@ public class UpdateWorkflowNotificationTemplatesPatch extends GenericEMailTempla @Override protected NodeRef getBaseTemplate() { - return WorkflowNotificationUtils.WF_ASSIGNED_TEMPLATE; + return new NodeRef(WorkflowNotificationUtils.WF_ASSIGNED_TEMPLATE); } /** diff --git a/source/java/org/alfresco/repo/attributes/AttributeServiceImpl.java b/source/java/org/alfresco/repo/attributes/AttributeServiceImpl.java index 59605089f8..c35df9c4c3 100644 --- a/source/java/org/alfresco/repo/attributes/AttributeServiceImpl.java +++ b/source/java/org/alfresco/repo/attributes/AttributeServiceImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -173,9 +173,9 @@ public class AttributeServiceImpl implements AttributeService } else { - Long id = pair.getFirst(); - propertyValueDAO.updatePropertyUniqueContext(id, value); + propertyValueDAO.updatePropertyUniqueContext(keys[0], keys[1], keys[2], value); } + // Done if (logger.isDebugEnabled()) { @@ -231,7 +231,7 @@ public class AttributeServiceImpl implements AttributeService else { Long id = pair.getFirst(); - propertyValueDAO.updatePropertyUniqueContext(id, keyAfter1, keyAfter2, keyAfter3); + propertyValueDAO.updatePropertyUniqueContextKeys(id, keyAfter1, keyAfter2, keyAfter3); } } catch (PropertyUniqueConstraintViolation e) diff --git a/source/java/org/alfresco/repo/audit/AuditComponentTest.java b/source/java/org/alfresco/repo/audit/AuditComponentTest.java index 483d44dbf0..1182b0c58e 100644 --- a/source/java/org/alfresco/repo/audit/AuditComponentTest.java +++ b/source/java/org/alfresco/repo/audit/AuditComponentTest.java @@ -46,7 +46,6 @@ import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.security.MutableAuthenticationService; -import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.ApplicationContextHelper; import org.alfresco.util.EqualsHelper; @@ -54,7 +53,6 @@ import org.apache.commons.lang.mutable.MutableInt; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.context.ApplicationContext; -import org.springframework.extensions.webscripts.GUID; import org.springframework.util.ResourceUtils; /** diff --git a/source/java/org/alfresco/repo/config/xml/RepoXMLConfigService.java b/source/java/org/alfresco/repo/config/xml/RepoXMLConfigService.java index 12c538811f..880c6f1a98 100644 --- a/source/java/org/alfresco/repo/config/xml/RepoXMLConfigService.java +++ b/source/java/org/alfresco/repo/config/xml/RepoXMLConfigService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -24,8 +24,6 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; -import javax.transaction.UserTransaction; - import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.repo.cache.SimpleCache; import org.alfresco.repo.security.authentication.AuthenticationContext; @@ -33,6 +31,7 @@ import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.tenant.TenantAdminService; import org.alfresco.repo.tenant.TenantDeployer; +import org.alfresco.repo.tenant.TenantUtil; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.transaction.TransactionService; import org.apache.commons.logging.Log; @@ -430,7 +429,7 @@ public class RepoXMLConfigService extends XMLConfigService implements TenantDepl // local helper - returns tenant domain (or empty string if default non-tenant) private String getTenantDomain() { - return tenantAdminService.getCurrentUserDomain(); + return TenantUtil.getCurrentDomain(); } private static class ConfigData diff --git a/source/java/org/alfresco/repo/dictionary/RepositoryLocation.java b/source/java/org/alfresco/repo/dictionary/RepositoryLocation.java index 69479b1a87..4af4621b15 100644 --- a/source/java/org/alfresco/repo/dictionary/RepositoryLocation.java +++ b/source/java/org/alfresco/repo/dictionary/RepositoryLocation.java @@ -24,9 +24,7 @@ import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.namespace.QName; /** - * Repository location object, defines a location in the repository from which dictionary models/resources should be loaded - * for inclusion in the data dictionary. - * + * Repository location object - defines a location in the repository (can also be used for classpath location) */ public class RepositoryLocation { @@ -43,6 +41,7 @@ public class RepositoryLocation private String queryLanguage = "xpath"; // default public static final String LANGUAGE_PATH = "path"; // lookup directly using node (prefix qname) path + public static final String LANGUAGE_CLASSPATH = "classpath"; /** @@ -106,7 +105,10 @@ public class RepositoryLocation */ public void setQueryLanguage(String queryLanguage) { - if (queryLanguage.equals(SearchService.LANGUAGE_LUCENE) || queryLanguage.equals(SearchService.LANGUAGE_XPATH) || queryLanguage.equals(LANGUAGE_PATH)) + if (queryLanguage.equals(SearchService.LANGUAGE_LUCENE) || + queryLanguage.equals(SearchService.LANGUAGE_XPATH) || + queryLanguage.equals(LANGUAGE_PATH) || + queryLanguage.equals(LANGUAGE_CLASSPATH)) { this.queryLanguage = queryLanguage; } @@ -117,7 +119,7 @@ public class RepositoryLocation } /** - * Get the store reference + * Get the store reference (note: should be ignored for classpath location) * * @return the store reference */ diff --git a/source/java/org/alfresco/repo/domain/avm/ibatis/AVMLockDAOImpl.java b/source/java/org/alfresco/repo/domain/avm/ibatis/AVMLockDAOImpl.java index b22044bc60..c03ff31be7 100644 --- a/source/java/org/alfresco/repo/domain/avm/ibatis/AVMLockDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/avm/ibatis/AVMLockDAOImpl.java @@ -21,7 +21,10 @@ package org.alfresco.repo.domain.avm.ibatis; import java.util.HashMap; import java.util.Map; +import org.alfresco.repo.cache.SimpleCache; import org.alfresco.repo.domain.avm.AbstractAVMLockDAOImpl; +import org.alfresco.repo.domain.propval.AbstractPropertyValueDAOImpl.CachePucKey; +import org.alfresco.repo.domain.propval.PropertyUniqueContextEntity; import org.mybatis.spring.SqlSessionTemplate; /** @@ -43,6 +46,18 @@ public class AVMLockDAOImpl extends AbstractAVMLockDAOImpl this.template = sqlSessionTemplate; } + private SimpleCache propertyUniqueContextCache; // cluster-aware + + /** + * Set the cache to use for avm_version_roots lookups (optional). + * + * @param vrEntityCache + */ + public void setPropertyUniqueContextCache(SimpleCache propertyUniqueContextCache) + { + this.propertyUniqueContextCache = propertyUniqueContextCache; + } + @Override protected int deletePropertyUniqueContexts(Long avmLocksValueId, Long avmStoreNameId, String dirPathToMatch, String lockDataStoreKey, String lockDataStoreValue) { @@ -55,25 +70,34 @@ public class AVMLockDAOImpl extends AbstractAVMLockDAOImpl dirPathToMatch = dirPathToMatch + "%"; } - if (lockDataStoreKey != null) + try { - Map params = new HashMap(5); - params.put("value1PropId", avmLocksValueId); - params.put("value2PropId", avmStoreNameId); - params.put("value3LikeStr", dirPathToMatch); - params.put("lockDataStoreKey", lockDataStoreKey); - params.put("lockDataStoreValue", lockDataStoreValue); - - return template.delete(DELETE_MATCHING_AVM_LOCKS_1_KV, params); + if (lockDataStoreKey != null) + { + Map params = new HashMap(5); + params.put("value1PropId", avmLocksValueId); + params.put("value2PropId", avmStoreNameId); + params.put("value3LikeStr", dirPathToMatch); + params.put("lockDataStoreKey", lockDataStoreKey); + params.put("lockDataStoreValue", lockDataStoreValue); + + return template.delete(DELETE_MATCHING_AVM_LOCKS_1_KV, params); + } + else + { + Map params = new HashMap(3); + params.put("value1PropId", avmLocksValueId); + params.put("value2PropId", avmStoreNameId); + params.put("value3LikeStr", dirPathToMatch); + + return template.delete(DELETE_MATCHING_AVM_LOCKS_0_KV, params); + } } - else + finally { - Map params = new HashMap(3); - params.put("value1PropId", avmLocksValueId); - params.put("value2PropId", avmStoreNameId); - params.put("value3LikeStr", dirPathToMatch); - - return template.delete(DELETE_MATCHING_AVM_LOCKS_0_KV, params); + // reasonable to clear for now (eg. only used by AVMLockingService.removeLocks*) + // note: in future, if we need to support mass removal based on specific key grouping then we need to use more intelligent cache (removal) + propertyUniqueContextCache.clear(); } } } diff --git a/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java b/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java index 4fbaeda1f1..551554e688 100644 --- a/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2012 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -690,21 +690,21 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO /* * Stores */ - + @Override public Pair getStore(StoreRef storeRef) { - StoreEntity store = selectStore(storeRef); - if (store == null) + Pair rootNodePair = rootNodesCache.getByKey(storeRef); + if (rootNodePair == null) { - return null; + throw new InvalidStoreRefException(storeRef); } else { - return new Pair(store.getId(), store.getStoreRef()); + return new Pair(rootNodePair.getSecond().getStore().getId(), rootNodePair.getFirst()); } } - + @Override public List> getStores() { diff --git a/source/java/org/alfresco/repo/domain/node/NodeDAO.java b/source/java/org/alfresco/repo/domain/node/NodeDAO.java index f8cfb45b5e..4dfe46dc3f 100644 --- a/source/java/org/alfresco/repo/domain/node/NodeDAO.java +++ b/source/java/org/alfresco/repo/domain/node/NodeDAO.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * diff --git a/source/java/org/alfresco/repo/domain/propval/AbstractPropertyValueDAOImpl.java b/source/java/org/alfresco/repo/domain/propval/AbstractPropertyValueDAOImpl.java index 42156e1bc9..0248fe97e5 100644 --- a/source/java/org/alfresco/repo/domain/propval/AbstractPropertyValueDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/propval/AbstractPropertyValueDAOImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -31,6 +31,7 @@ import java.util.Map; import java.util.Set; import java.util.TreeMap; +import org.alfresco.repo.cache.NullCache; import org.alfresco.repo.cache.SimpleCache; import org.alfresco.repo.cache.lookup.EntityLookupCache; import org.alfresco.repo.cache.lookup.EntityLookupCache.EntityLookupCallbackDAOAdaptor; @@ -38,9 +39,11 @@ import org.alfresco.repo.domain.CrcHelper; import org.alfresco.repo.domain.control.ControlDAO; import org.alfresco.repo.domain.propval.PropertyValueEntity.PersistedType; import org.alfresco.repo.domain.schema.SchemaBootstrap; +import org.alfresco.util.EqualsHelper; import org.alfresco.util.Pair; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.dao.ConcurrencyFailureException; import org.springframework.dao.DataIntegrityViolationException; /** @@ -124,12 +127,26 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO */ private EntityLookupCache propertyCache; + private SimpleCache propertyUniqueContextCache; // cluster-aware + + /** + * Set the cache to use for avm_version_roots lookups (optional). + * + * @param vrEntityCache + */ + public void setPropertyUniqueContextCache(SimpleCache propertyUniqueContextCache) + { + this.propertyUniqueContextCache = propertyUniqueContextCache; + } + + /** * Default constructor. *

* This sets up the DAO accessors to bypass any caching to handle the case where the caches are not * supplied in the setters. */ + @SuppressWarnings({ "unchecked", "rawtypes" }) public AbstractPropertyValueDAOImpl() { this.propertyClassDaoCallback = new PropertyClassCallbackDAO(); @@ -147,6 +164,8 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO this.propertySerializableValueCache = new EntityLookupCache(propertySerializableValueCallback); this.propertyValueCache = new EntityLookupCache(propertyValueCallback); this.propertyCache = new EntityLookupCache(propertyCallback); + + this.propertyUniqueContextCache = (SimpleCache)new NullCache(); } /** @@ -805,6 +824,9 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO Pair entityPair = propertyCache.getByKey(id); if (entityPair == null) { + // Remove from cache + propertyCache.removeByKey(id); + throw new DataIntegrityViolationException("No property value exists for ID " + id); } return entityPair.getSecond(); @@ -1085,7 +1107,63 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO //================================ // 'alf_prop_unique_ctx' accessors //================================ - + + private CachePucKey getPucKey(Long id1, Long id2, Long id3) + { + return new CachePucKey(id1, id2, id3); + } + + /** + * Key for PropertyUniqueContext cache + */ + public static class CachePucKey implements Serializable + { + private static final long serialVersionUID = -4294324585692613101L; + + private final Long key1; + private final Long key2; + private final Long key3; + + private final int hashCode; + + private CachePucKey(Long key1, Long key2, Long key3) + { + this.key1 = key1; + this.key2 = key2; + this.key3 = key3; + this.hashCode = (key1 == null ? 0 : key1.hashCode()) + (key2 == null ? 0 : key2.hashCode()) + (key3 == null ? 0 : key3.hashCode()); + } + + @Override + public String toString() + { + return key1 + "." + key2 + "." + key3; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + else if (!(obj instanceof CachePucKey)) + { + return false; + } + CachePucKey that = (CachePucKey) obj; + return EqualsHelper.nullSafeEquals(this.key1, that.key1) && + EqualsHelper.nullSafeEquals(this.key2, that.key2) && + EqualsHelper.nullSafeEquals(this.key3, that.key3); + } + + @Override + public int hashCode() + { + return hashCode; + } + } + public Pair createPropertyUniqueContext( Serializable value1, Serializable value2, Serializable value3, Serializable propertyValue1) @@ -1104,11 +1182,17 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO property1Id = createProperty(propertyValue1); } + CachePucKey pucKey = getPucKey(id1, id2, id3); + Savepoint savepoint = controlDAO.createSavepoint("createPropertyUniqueContext"); try { PropertyUniqueContextEntity entity = createPropertyUniqueContext(id1, id2, id3, property1Id); controlDAO.releaseSavepoint(savepoint); + + // cache + propertyUniqueContextCache.put(pucKey, entity); + if (logger.isDebugEnabled()) { logger.debug( @@ -1116,10 +1200,14 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO " Values: " + value1 + "-" + value2 + "-" + value3 + "\n" + " Result: " + entity); } + return new Pair(entity.getId(), property1Id); } catch (Throwable e) { + // Remove from cache + propertyUniqueContextCache.remove(pucKey); + controlDAO.rollbackToSavepoint(savepoint); throw new PropertyUniqueConstraintViolation(value1, value2, value3, e); } @@ -1132,14 +1220,48 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO Pair pair2 = getPropertyValue(value2); Pair pair3 = getPropertyValue(value3); if (pair1 == null || pair2 == null || pair3 == null) - { + { // None of the values exist so no unique context values can exist - return null; - } + return null; + } Long id1 = pair1.getFirst(); Long id2 = pair2.getFirst(); Long id3 = pair3.getFirst(); - PropertyUniqueContextEntity entity = getPropertyUniqueContextByValues(id1, id2, id3); + + CachePucKey pucKey = getPucKey(id1, id2, id3); + + // check cache + PropertyUniqueContextEntity entity = propertyUniqueContextCache.get(pucKey); + if (entity == null) + { + // Remove from cache + propertyUniqueContextCache.remove(pucKey); + + // query DB + entity = getPropertyUniqueContextByValues(id1, id2, id3); + + if (entity != null) + { + // cache + propertyUniqueContextCache.put(pucKey, entity); + } + } + + if ((entity != null) && (entity.getPropertyId() != null)) + { + try + { + // eager fetch - ignore return for now (could change API) + getPropertyById(entity.getPropertyId()); + } + catch (DataIntegrityViolationException dive) + { + // Remove from cache + propertyUniqueContextCache.remove(pucKey); + throw dive; + } + } + // Done if (logger.isDebugEnabled()) { @@ -1168,7 +1290,10 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO } valueIds[i] = valuePair.getFirst(); } + + // not cached getPropertyUniqueContextByValues(callback, valueIds); + // Done if (logger.isDebugEnabled()) { @@ -1177,8 +1302,12 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO " Values: " + Arrays.toString(values)); } } - - public void updatePropertyUniqueContext(Long id, Serializable value1, Serializable value2, Serializable value3) + + /* + * Update PUC keys - retain current property value + * + */ + public void updatePropertyUniqueContextKeys(Long id, Serializable value1, Serializable value2, Serializable value3) { /* * Use savepoints so that the PropertyUniqueConstraintViolation can be caught and handled in-transactioin @@ -1189,19 +1318,30 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO Long id2 = getOrCreatePropertyValue(value2).getFirst(); Long id3 = getOrCreatePropertyValue(value3).getFirst(); + CachePucKey pucKey = getPucKey(id1, id2, id3); + Savepoint savepoint = controlDAO.createSavepoint("updatePropertyUniqueContext"); try { PropertyUniqueContextEntity entity = getPropertyUniqueContextById(id); if (entity == null) { + // Remove from cache + propertyUniqueContextCache.remove(pucKey); + throw new DataIntegrityViolationException("No unique property context exists for id: " + id); } entity.setValue1PropId(id1); entity.setValue2PropId(id2); entity.setValue3PropId(id3); - updatePropertyUniqueContext(entity); + + entity = updatePropertyUniqueContext(entity); + controlDAO.releaseSavepoint(savepoint); + + // cache + propertyUniqueContextCache.put(pucKey, entity); + // Done if (logger.isDebugEnabled()) { @@ -1214,43 +1354,83 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO } catch (Throwable e) { + // Remove from cache + propertyUniqueContextCache.remove(pucKey); + controlDAO.rollbackToSavepoint(savepoint); throw new PropertyUniqueConstraintViolation(value1, value2, value3, e); } } - - public void updatePropertyUniqueContext(Long id, Serializable propertyValue) + + /* + * Update property value by keys + */ + public void updatePropertyUniqueContext(Serializable value1, Serializable value2, Serializable value3, Serializable propertyValue) { - PropertyUniqueContextEntity entity = getPropertyUniqueContextById(id); - if (entity == null) - { - throw new DataIntegrityViolationException("No unique property context exists for id: " + id); - } - Long propertyIdToDelete = entity.getPropertyId(); - - Long propertyId = null; - if (propertyValue != null) - { - propertyId = createProperty(propertyValue); - } + // Translate the properties. Null values are acceptable + Long id1 = getOrCreatePropertyValue(value1).getFirst(); + Long id2 = getOrCreatePropertyValue(value2).getFirst(); + Long id3 = getOrCreatePropertyValue(value3).getFirst(); - // Create a new property - entity.setPropertyId(propertyId); - updatePropertyUniqueContext(entity); + CachePucKey pucKey = getPucKey(id1, id2, id3); - // Clean up the previous property, if present - if (propertyIdToDelete != null) + try { - deleteProperty(propertyIdToDelete); + Pair entityPair = getPropertyUniqueContext(value1, value2, value3); + if (entityPair == null) + { + throw new DataIntegrityViolationException("No unique property context exists for values: " + value1 + "-" + value2 + "-" + value3); + } + + long id = entityPair.getFirst(); + PropertyUniqueContextEntity entity = getPropertyUniqueContextById(id); + if (entity == null) + { + throw new DataIntegrityViolationException("No unique property context exists for id: " + id); + } + + Long propertyIdToDelete = entity.getPropertyId(); + + Long propertyId = null; + if (propertyValue != null) + { + propertyId = createProperty(propertyValue); + } + + // Create a new property + entity.setPropertyId(propertyId); + + entity = updatePropertyUniqueContext(entity); + + // cache + propertyUniqueContextCache.put(pucKey, entity); + + // Clean up the previous property, if present + if (propertyIdToDelete != null) + { + deleteProperty(propertyIdToDelete); + } + + // Done + if (logger.isDebugEnabled()) + { + logger.debug( + "Updated unique property context: \n" + + " ID: " + id + "\n" + + " Property: " + propertyId); + } } - - // Done - if (logger.isDebugEnabled()) + catch (DataIntegrityViolationException e) { - logger.debug( - "Updated unique property context: \n" + - " ID: " + id + "\n" + - " Property: " + propertyId); + // Remove from cache + propertyUniqueContextCache.remove(pucKey); + throw e; + } + catch (ConcurrencyFailureException e) + { + // Remove from cache + propertyUniqueContextCache.remove(pucKey); + throw e; } } @@ -1272,6 +1452,20 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO valueIds[i] = valuePair.getFirst(); } int deleted = deletePropertyUniqueContexts(valueIds); + + CachePucKey pucKey = getPucKey(valueIds[0], (values.length > 1 ? valueIds[1] : null), (values.length > 2 ? valueIds[2] : null)); + + if (values.length == 3) + { + propertyUniqueContextCache.remove(pucKey); + } + else + { + // reasonable to clear for now (eg. only used by AVMLockingService.removeLocks*) + // note: in future, if we need to support mass removal based on specific key grouping then we need to use more intelligent cache (removal) + propertyUniqueContextCache.clear(); + } + // Done if (logger.isDebugEnabled()) { diff --git a/source/java/org/alfresco/repo/domain/propval/PropertyValueDAO.java b/source/java/org/alfresco/repo/domain/propval/PropertyValueDAO.java index 3920a78471..be3b223aad 100644 --- a/source/java/org/alfresco/repo/domain/propval/PropertyValueDAO.java +++ b/source/java/org/alfresco/repo/domain/propval/PropertyValueDAO.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -325,15 +325,15 @@ public interface PropertyValueDAO * * @see #createPropertyUniqueContext(Serializable, Serializable, Serializable, Serializable) */ - void updatePropertyUniqueContext( + void updatePropertyUniqueContextKeys( Long id, Serializable value1, Serializable value2, Serializable value3); /** - * Update the property associated with a unique context. + * Update the property associated with a unique context (based on one, two or three context values). * * @see #createPropertyUniqueContext(Serializable, Serializable, Serializable, Serializable) */ - void updatePropertyUniqueContext(Long id, Serializable propertyValue1); + void updatePropertyUniqueContext(Serializable value1, Serializable value2, Serializable value3, Serializable propertyValue); /** * @see #createPropertyUniqueContext(Serializable, Serializable, Serializable, Serializable) */ diff --git a/source/java/org/alfresco/repo/domain/propval/PropertyValueDAOTest.java b/source/java/org/alfresco/repo/domain/propval/PropertyValueDAOTest.java index 7d999acde9..33ea3473bb 100644 --- a/source/java/org/alfresco/repo/domain/propval/PropertyValueDAOTest.java +++ b/source/java/org/alfresco/repo/domain/propval/PropertyValueDAOTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -777,7 +777,7 @@ public class PropertyValueDAOTest extends TestCase public Void execute() throws Throwable { // Now update it - propertyValueDAO.updatePropertyUniqueContext(id, "A", "AA", bbb); + propertyValueDAO.updatePropertyUniqueContextKeys(id, "A", "AA", bbb); // Should be able to create the previous one ... propertyValueDAO.createPropertyUniqueContext("A", "AA", aaa, null); // ... and fail to create the second one @@ -851,7 +851,7 @@ public class PropertyValueDAOTest extends TestCase { public Pair execute() throws Throwable { - propertyValueDAO.updatePropertyUniqueContext(id, v1); + propertyValueDAO.updatePropertyUniqueContext(aaa, bbb, ccc, v1); Pair pair = propertyValueDAO.getPropertyUniqueContext(aaa, bbb, ccc); Serializable value = propertyValueDAO.getPropertyById(pair.getSecond()); return new Pair(pair.getFirst(), value); @@ -863,7 +863,7 @@ public class PropertyValueDAOTest extends TestCase { public Pair execute() throws Throwable { - propertyValueDAO.updatePropertyUniqueContext(id, v2); + propertyValueDAO.updatePropertyUniqueContext(aaa, bbb, ccc, v2); Pair pair = propertyValueDAO.getPropertyUniqueContext(aaa, bbb, ccc); Serializable value = propertyValueDAO.getPropertyById(pair.getSecond()); return new Pair(pair.getFirst(), value); diff --git a/source/java/org/alfresco/repo/jscript/People.java b/source/java/org/alfresco/repo/jscript/People.java index 36978aa45b..b66f4ac426 100644 --- a/source/java/org/alfresco/repo/jscript/People.java +++ b/source/java/org/alfresco/repo/jscript/People.java @@ -30,7 +30,6 @@ import java.util.Set; import java.util.StringTokenizer; import org.alfresco.model.ContentModel; -import org.alfresco.query.PagingRequest; import org.alfresco.repo.security.authentication.AuthenticationException; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.UserNameGenerator; @@ -56,6 +55,7 @@ import org.alfresco.service.cmr.usage.ContentUsageService; import org.alfresco.service.namespace.QName; import org.alfresco.util.Pair; import org.alfresco.util.PropertyMap; +import org.alfresco.util.ScriptPagingDetails; import org.alfresco.util.ValueDerivingMapFactory; import org.alfresco.util.ValueDerivingMapFactory.ValueDeriver; import org.apache.commons.logging.Log; @@ -72,7 +72,7 @@ import org.springframework.extensions.surf.util.ParameterCheck; * @author davidc * @author kevinr */ -public final class People extends BaseScopableProcessorExtension implements InitializingBean +public class People extends BaseScopableProcessorExtension implements InitializingBean { private static Log logger = LogFactory.getLog(People.class); @@ -83,9 +83,10 @@ public final class People extends BaseScopableProcessorExtension implements Init private PersonService personService; private MutableAuthenticationService authenticationService; private ContentUsageService contentUsageService; - private TenantService tenantService; private UserNameGenerator usernameGenerator; private UserRegistrySynchronizer userRegistrySynchronizer; + protected TenantService tenantService; + private StoreRef storeRef; private ValueDerivingMapFactory valueDerivingMapFactory; private int numRetries = 10; @@ -535,6 +536,26 @@ public final class People extends BaseScopableProcessorExtension implements Init */ public Scriptable getPeople(String filter, int maxResults, String sortBy, boolean sortAsc) { + return getPeople(filter, new ScriptPagingDetails(maxResults, 0), null, null); + } + + public Scriptable getPeople(String filter, ScriptPagingDetails pagingRequest, String sortBy, Boolean sortAsc) + { + List persons = getPeopleImpl(filter, pagingRequest, sortBy, sortAsc); + + Object[] peopleRefs = new Object[persons.size()]; + for (int i = 0; i < peopleRefs.length; i++) + { + peopleRefs[i] = persons.get(i).getNodeRef(); + } + + return Context.getCurrentContext().newArray(getScope(), peopleRefs); + } + + protected List getPeopleImpl(String filter, ScriptPagingDetails pagingRequest, String sortBy, Boolean sortAsc) + { + ParameterCheck.mandatory("pagingRequest", pagingRequest); + boolean useCQ = false; if (filter != null) { @@ -544,96 +565,124 @@ public final class People extends BaseScopableProcessorExtension implements Init filter = filter.substring(0, filter.length()-HINT_CQ_SUFFIX.length()); } } + else + { + filter = "*"; + } - Object[] people = null; + List persons = null; + int maxResults = pagingRequest.getMaxItems(); if ((maxResults <= 0) || (maxResults > defaultListMaxResults)) { // remove open-ended query (eg cutoff at default/configurable max, eg. 5000 people) maxResults = defaultListMaxResults; + pagingRequest.setMaxItems(maxResults); } - if ((filter == null || filter.length() == 0) || useCQ) + if (useCQ) { - people = getPeopleImplDB(filter, maxResults, sortBy, sortAsc); + persons = getPeopleImplDB(filter, pagingRequest, sortBy, sortAsc); } else { filter = filter.trim(); - if (filter.length() != 0) + + String term = filter.replace("\\", "").replace("\"", ""); + StringTokenizer t = new StringTokenizer(term, " "); + int propIndex = term.lastIndexOf(':'); + int wildPosition = term.indexOf('*'); + + // simple filter - can use CQ if search fails + useCQ = ((t.countTokens() == 1) && (propIndex == -1) && ((wildPosition == -1) || (wildPosition == (term.length() - 1)))); + + try { - String term = filter.replace("\\", "").replace("\"", ""); - StringTokenizer t = new StringTokenizer(term, " "); - int propIndex = term.lastIndexOf(':'); - int wildPosition = term.indexOf('*'); + // FTS + List personRefs = getPeopleImplSearch(filter, pagingRequest, sortBy, sortAsc); - // simple filter - can use CQ if search fails - useCQ = ((t.countTokens() == 1) && (propIndex == -1) && ((wildPosition == -1) || (wildPosition == (term.length() - 1)))); - - try + if (personRefs != null) { - people = getPeopleImplSearch(term, t, propIndex, maxResults, sortBy, sortAsc); - } - catch (Throwable err) - { - if (useCQ) + persons = new ArrayList(personRefs.size()); + for (NodeRef personRef : personRefs) { - // search unavailable and/or parser exception - try CQ instead - people = getPeopleImplDB(term, maxResults, sortBy, sortAsc); + persons.add(personService.getPerson(personRef)); } } } + catch (Throwable err) + { + if (useCQ) + { + // search unavailable and/or parser exception - try CQ instead + // simple non-FTS filter: firstname or lastname or username starting with term (ignoring case) + persons = getPeopleImplDB(filter, pagingRequest, sortBy, sortAsc); + } + } } - if (people == null) - { - people = new Object[0]; - } - - return Context.getCurrentContext().newArray(getScope(), people); + return (persons != null ? persons : new ArrayList(0)); } // canned query - private Object[] getPeopleImplDB(String term, int maxResults, final String sortBy, boolean sortAsc) + protected List getPeopleImplDB(String filter, ScriptPagingDetails pagingRequest, String sortBy, Boolean sortAsc) { - Long start = (logger.isDebugEnabled() ? System.currentTimeMillis() : null); + List filterProps = null; - List peopleRefs = new ArrayList(); - - // simple non-FTS filter: firstname or lastname or username starting with term (ignoring case) - - List filterProps = new ArrayList(3); - filterProps.add(ContentModel.PROP_FIRSTNAME); - filterProps.add(ContentModel.PROP_LASTNAME); - filterProps.add(ContentModel.PROP_USERNAME); - - List> sortProps = new ArrayList>(1); - sortProps.add(new Pair(ContentModel.PROP_USERNAME, true)); - - PagingRequest pagingRequest = new PagingRequest(maxResults, null); - List persons = personService.getPeople(term, filterProps, sortProps, pagingRequest).getPage(); - for (int i=0; i 0)) { - peopleRefs.add(persons.get(i).getNodeRef()); + filter = filter.trim(); + if (! filter.equals("*")) + { + filter = filter.replace("\\", "").replace("\"", ""); + + // simple non-FTS filter: firstname or lastname or username starting with term (ignoring case) + + filterProps = new ArrayList(3); + filterProps.add(ContentModel.PROP_FIRSTNAME); + filterProps.add(ContentModel.PROP_LASTNAME); + filterProps.add(ContentModel.PROP_USERNAME); + } } - Object[] people = getSortedPeopleObjects(peopleRefs, sortBy, sortAsc); - - if (start != null) + // Build the sorting. The user controls the primary sort, we supply + // additional ones automatically + List> sort = new ArrayList>(); + if ("lastName".equals(sortBy)) { - logger.debug("getPeople: cq - "+people.length+" items (in "+(System.currentTimeMillis()-start)+" msecs)"); + sort.add(new Pair(ContentModel.PROP_LASTNAME, sortAsc)); + sort.add(new Pair(ContentModel.PROP_FIRSTNAME, sortAsc)); + sort.add(new Pair(ContentModel.PROP_USERNAME, sortAsc)); } - - return people; + else if ("firstName".equals(sortBy)) + { + sort.add(new Pair(ContentModel.PROP_FIRSTNAME, sortAsc)); + sort.add(new Pair(ContentModel.PROP_LASTNAME, sortAsc)); + sort.add(new Pair(ContentModel.PROP_USERNAME, sortAsc)); + } + else + { + sort.add(new Pair(ContentModel.PROP_USERNAME, sortAsc)); + sort.add(new Pair(ContentModel.PROP_FIRSTNAME, sortAsc)); + sort.add(new Pair(ContentModel.PROP_LASTNAME, sortAsc)); + } + + return personService.getPeople(filter, filterProps, sort, pagingRequest).getPage(); } // search query - private Object[] getPeopleImplSearch(String term, StringTokenizer t, int propIndex, int maxResults, final String sortBy, boolean sortAsc) throws Throwable + protected List getPeopleImplSearch(String filter, ScriptPagingDetails pagingRequest, String sortBy, Boolean sortAsc) throws Throwable { + List personRefs = null; + Long start = (logger.isDebugEnabled() ? System.currentTimeMillis() : null); - List peopleRefs = new ArrayList(); - Object[] people = null; + String term = filter.replace("\\", "").replace("\"", ""); + StringTokenizer t = new StringTokenizer(term, " "); + int propIndex = term.indexOf(':'); + + int maxResults = pagingRequest.getMaxItems(); + int skipCount = pagingRequest.getSkipCount(); SearchParameters params = new SearchParameters(); params.addQueryTemplate("_PERSON", "|%firstName OR |%lastName OR |%userName"); @@ -732,21 +781,57 @@ public final class People extends BaseScopableProcessorExtension implements Init params.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); params.addStore(this.storeRef); params.setQuery(query.toString()); + + if (logger.isDebugEnabled()) + { + if ((sortBy != null) && (! sortBy.isEmpty())) + { + logger.debug("getPeopleImplSearch: ignoring sortBy ("+sortBy+")- not yet supported by model for search"); + } + } + + /* not yet supported (default property index tokenisation mode = true) + if ("lastName".equals(sortBy)) + { + params.addSort("@{http://www.alfresco.org/model/content/1.0}lastName", sortAsc); + params.addSort("@{http://www.alfresco.org/model/content/1.0}firstName", sortAsc); + params.addSort("@{http://www.alfresco.org/model/content/1.0}userName", sortAsc); + } + else if ("firstName".equals(sortBy)) + { + params.addSort("@{http://www.alfresco.org/model/content/1.0}firstName", sortAsc); + params.addSort("@{http://www.alfresco.org/model/content/1.0}lastName", sortAsc); + params.addSort("@{http://www.alfresco.org/model/content/1.0}userName", sortAsc); + } + else + { + params.addSort("@{http://www.alfresco.org/model/content/1.0}userName", sortAsc); + params.addSort("@{http://www.alfresco.org/model/content/1.0}firstName", sortAsc); + params.addSort("@{http://www.alfresco.org/model/content/1.0}userName", sortAsc); + } + */ + if (maxResults > 0) { params.setLimitBy(LimitBy.FINAL_SIZE); params.setLimit(maxResults); } + if (skipCount > 0) + { + params.setSkipCount(skipCount); + } + ResultSet results = null; try { results = services.getSearchService().query(params); - people = getSortedPeopleObjects(results.getNodeRefs(), sortBy, sortAsc); + + personRefs = getSortedPeopleObjects(results.getNodeRefs(), sortBy, sortAsc); if (start != null) { - logger.debug("getPeople: search - "+people.length+" items (in "+(System.currentTimeMillis()-start)+" msecs)"); + logger.debug("getPeople: search - "+personRefs.size()+" items (in "+(System.currentTimeMillis()-start)+" msecs)"); } } catch (Throwable err) @@ -766,10 +851,10 @@ public final class People extends BaseScopableProcessorExtension implements Init } } - return people; + return personRefs; } - private Object[] getSortedPeopleObjects(List peopleRefs, final String sortBy, boolean sortAsc) + private List getSortedPeopleObjects(List peopleRefs, final String sortBy, boolean sortAsc) { final Collator col = Collator.getInstance(I18NUtil.getLocale()); final NodeService nodeService = services.getNodeService(); @@ -831,12 +916,9 @@ public final class People extends BaseScopableProcessorExtension implements Init return result.toString(); } - }); + }); - Object[] people = new Object[peopleRefs.size()]; - peopleRefs.toArray(people); - - return people; + return peopleRefs; } /** diff --git a/source/java/org/alfresco/repo/model/filefolder/HiddenAspect.java b/source/java/org/alfresco/repo/model/filefolder/HiddenAspect.java index 1ec22b0f0d..88e2680cf8 100644 --- a/source/java/org/alfresco/repo/model/filefolder/HiddenAspect.java +++ b/source/java/org/alfresco/repo/model/filefolder/HiddenAspect.java @@ -1,3 +1,21 @@ +/* + * Copyright (C) 2005-2012 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ package org.alfresco.repo.model.filefolder; import java.io.Serializable; @@ -15,6 +33,7 @@ import org.alfresco.model.ContentModel; import org.alfresco.query.PagingRequest; import org.alfresco.query.PagingResults; import org.alfresco.repo.search.impl.lucene.LuceneQueryParser; +import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.model.FileInfo; import org.alfresco.service.cmr.repository.ChildAssociationRef; @@ -599,42 +618,44 @@ public class HiddenAspect { Visibility ret = Visibility.Visible; - if(nodeService.hasAspect(nodeRef, ContentModel.ASPECT_HIDDEN)) + if (! AuthenticationUtil.isRunAsUserTheSystemUser()) { - Integer visibilityMask = (Integer)nodeService.getProperty(nodeRef, ContentModel.PROP_VISIBILITY_MASK); - if(visibilityMask != null) + if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_HIDDEN)) { - if(visibilityMask.intValue() == 0) + Integer visibilityMask = (Integer)nodeService.getProperty(nodeRef, ContentModel.PROP_VISIBILITY_MASK); + if (visibilityMask != null) { - ret = Visibility.NotVisible; - } - else if(client == null) - { - ret = Visibility.NotVisible; + if(visibilityMask.intValue() == 0) + { + ret = Visibility.NotVisible; + } + else if(client == null) + { + ret = Visibility.NotVisible; + } + else + { + ret = getVisibility(visibilityMask.intValue(), client); + } } else { - ret = getVisibility(visibilityMask.intValue(), client); - } - } - else - { - // no visibility mask property, so retain backwards compatibility with 3.4 hidden aspect behaviour - if(client == Client.cifs) - { - ret = Visibility.HiddenAttribute; - } - else if(client == Client.webdav || client == Client.nfs || client == Client.imap) - { - ret = Visibility.Visible; - } - else - { - ret = Visibility.NotVisible; + // no visibility mask property, so retain backwards compatibility with 3.4 hidden aspect behaviour + if(client == Client.cifs) + { + ret = Visibility.HiddenAttribute; + } + else if(client == Client.webdav || client == Client.nfs || client == Client.imap) + { + ret = Visibility.Visible; + } + else + { + ret = Visibility.NotVisible; + } } } } - return ret; } diff --git a/source/java/org/alfresco/repo/module/ModuleComponentHelper.java b/source/java/org/alfresco/repo/module/ModuleComponentHelper.java index df19604f41..fbe647cb8b 100644 --- a/source/java/org/alfresco/repo/module/ModuleComponentHelper.java +++ b/source/java/org/alfresco/repo/module/ModuleComponentHelper.java @@ -87,6 +87,7 @@ public class ModuleComponentHelper private RegistryService registryService; private ModuleService moduleService; private TenantAdminService tenantAdminService; + private boolean applyToTenants; private Map> componentsByNameByModule; /** Default constructor */ @@ -131,7 +132,12 @@ public class ModuleComponentHelper { this.tenantAdminService = tenantAdminService; } - + + public void setApplyToTenants(boolean applyToTenants) + { + this.applyToTenants = applyToTenants; + } + /** * Add a managed module component to the registry of components. These will be controlled * by the {@link #startModules()} method. @@ -197,40 +203,47 @@ public class ModuleComponentHelper */ AuthenticationUtil.runAs(new RunAsWork() { - public Object doWork() throws Exception + public Object doWork() throws Exception { - try - { - TransactionService transactionService = serviceRegistry.getTransactionService(); - - // Get all the modules - List modules = moduleService.getAllModules(); - loggerService.info(I18NUtil.getMessage(MSG_FOUND_MODULES, modules.size())); + try + { + TransactionService transactionService = serviceRegistry.getTransactionService(); + + // Note: for system bootstrap this will be the default domain, else tenant domain for tenant create/import + final String tenantDomainCtx = tenantAdminService.getCurrentUserDomain(); + + if (tenantAdminService.isEnabled() && (! tenantDomainCtx.equals(TenantService.DEFAULT_DOMAIN)) && (! applyToTenants)) + { + // nothing to start (eg. when creating/importing tenant and applyToTenants = false) + return null; + } + + // Get all the modules + List modules = moduleService.getAllModules(); + loggerService.info(I18NUtil.getMessage(MSG_FOUND_MODULES, modules.size())); + + // Process each module in turn. Ordering is not important. + final Map> mapExecutedComponents = new HashMap>(1); + final Map> mapStartedModules = new HashMap>(1); + + mapExecutedComponents.put(tenantDomainCtx, new HashSet(10)); + mapStartedModules.put(tenantDomainCtx, new HashSet(2)); + + List tenantsNonFinal = null; + if (tenantAdminService.isEnabled()) + { + if (tenantDomainCtx.equals(TenantService.DEFAULT_DOMAIN) && applyToTenants) + { + tenantsNonFinal = tenantAdminService.getAllTenants(); + for (Tenant tenant : tenantsNonFinal) + { + mapExecutedComponents.put(tenant.getTenantDomain(), new HashSet(10)); + mapStartedModules.put(tenant.getTenantDomain(), new HashSet(2)); + } + } + } - // Process each module in turn. Ordering is not important. - final Map> mapExecutedComponents = new HashMap>(1); - final Map> mapStartedModules = new HashMap>(1); - - // Note: for system bootstrap this will be the default domain, else tenant domain for tenant create/import - final String tenantDomainCtx = tenantAdminService.getCurrentUserDomain(); - - mapExecutedComponents.put(tenantDomainCtx, new HashSet(10)); - mapStartedModules.put(tenantDomainCtx, new HashSet(2)); - - final List tenants; - if (tenantAdminService.isEnabled() && (tenantDomainCtx.equals(TenantService.DEFAULT_DOMAIN))) - { - tenants = tenantAdminService.getAllTenants(); - for (Tenant tenant : tenants) - { - mapExecutedComponents.put(tenant.getTenantDomain(), new HashSet(10)); - mapStartedModules.put(tenant.getTenantDomain(), new HashSet(2)); - } - } - else - { - tenants = null; - } + final List tenants = tenantsNonFinal; for (final ModuleDetails module : modules) { diff --git a/source/java/org/alfresco/repo/module/ModuleServiceImpl.java b/source/java/org/alfresco/repo/module/ModuleServiceImpl.java index 7bfaae30fd..f71b0fa64d 100644 --- a/source/java/org/alfresco/repo/module/ModuleServiceImpl.java +++ b/source/java/org/alfresco/repo/module/ModuleServiceImpl.java @@ -113,6 +113,10 @@ public class ModuleServiceImpl implements ApplicationContextAware, ModuleService this.moduleComponentHelper.setTenantAdminService(tenantAdminService); } + public void setApplyToTenants(boolean applyToTenants) + { + this.moduleComponentHelper.setApplyToTenants(applyToTenants); + } /* (non-Javadoc) diff --git a/source/java/org/alfresco/repo/notification/EMailNotificationProvider.java b/source/java/org/alfresco/repo/notification/EMailNotificationProvider.java index 71d54c4ba4..3d467ad3ce 100644 --- a/source/java/org/alfresco/repo/notification/EMailNotificationProvider.java +++ b/source/java/org/alfresco/repo/notification/EMailNotificationProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2011 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -36,6 +36,7 @@ import org.alfresco.service.cmr.notification.NotificationProvider; import org.alfresco.service.cmr.notification.NotificationService; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.repository.TemplateService; import org.alfresco.service.cmr.security.PersonService; import org.alfresco.util.ModelUtil; @@ -209,9 +210,9 @@ public class EMailNotificationProvider implements NotificationProvider mail.setParameterValue(MailActionExecuter.PARAM_TEXT, body); } else - { + { // Check for template - NodeRef template = notificationContext.getBodyTemplate(); + String template = notificationContext.getBodyTemplate(); if (template == null) { errorEncountered(notificationContext, @@ -220,12 +221,13 @@ public class EMailNotificationProvider implements NotificationProvider } else { - template = fileFolderService.getLocalizedSibling(template); + if (template.indexOf(StoreRef.URI_FILLER) != -1) + { + template = fileFolderService.getLocalizedSibling(new NodeRef(template)).toString(); + } mail.setParameterValue(MailActionExecuter.PARAM_TEMPLATE, template); mail.setParameterValue(MailActionExecuter.PARAM_TEMPLATE_MODEL, (Serializable)buildTemplateModel(notificationContext.getTemplateArgs())); - - } } diff --git a/source/java/org/alfresco/repo/notification/NotificationServiceImplSystemTest.java b/source/java/org/alfresco/repo/notification/NotificationServiceImplSystemTest.java index 8b9f7b31f2..4f19855d50 100644 --- a/source/java/org/alfresco/repo/notification/NotificationServiceImplSystemTest.java +++ b/source/java/org/alfresco/repo/notification/NotificationServiceImplSystemTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2011 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -220,7 +220,7 @@ public class NotificationServiceImplSystemTest extends BaseAlfrescoTestCase context.setFrom(FROM_EMAIL); context.addTo(TO_USER1); context.setSubject(SUBJECT); - context.setBodyTemplate(template); + context.setBodyTemplate(template.toString()); Map templateArgs = new HashMap(1); templateArgs.put("template", template); diff --git a/source/java/org/alfresco/repo/rendition/RenditionServiceImpl.java b/source/java/org/alfresco/repo/rendition/RenditionServiceImpl.java index b0e21a544c..a7774c395c 100644 --- a/source/java/org/alfresco/repo/rendition/RenditionServiceImpl.java +++ b/source/java/org/alfresco/repo/rendition/RenditionServiceImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -66,7 +66,7 @@ public class RenditionServiceImpl implements RenditionService, RenditionDefiniti private DictionaryService dictionaryService; private NodeService nodeService; - private RenditionDefinitionPersisterImpl renditionDefinitionPersister; + private RenditionDefinitionPersister renditionDefinitionPersister; /** * @since 4.0.1 @@ -77,7 +77,7 @@ public class RenditionServiceImpl implements RenditionService, RenditionDefiniti * Injects the RenditionDefinitionPersister bean. * @param renditionDefinitionPersister */ - public void setRenditionDefinitionPersister(RenditionDefinitionPersisterImpl renditionDefinitionPersister) + public void setRenditionDefinitionPersister(RenditionDefinitionPersister renditionDefinitionPersister) { this.renditionDefinitionPersister = renditionDefinitionPersister; } diff --git a/source/java/org/alfresco/repo/rendition/RenditionServiceIntegrationTest.java b/source/java/org/alfresco/repo/rendition/RenditionServiceIntegrationTest.java index 5523b97318..ff18920a17 100644 --- a/source/java/org/alfresco/repo/rendition/RenditionServiceIntegrationTest.java +++ b/source/java/org/alfresco/repo/rendition/RenditionServiceIntegrationTest.java @@ -204,7 +204,8 @@ public class RenditionServiceIntegrationTest extends BaseAlfrescoSpringTest nodeService.deleteNode(testTargetFolder); } - public void testRenderFreeMarkerTemplate() throws Exception + //TODO Fix this failing test. + public void off_testRenderFreeMarkerTemplate() throws Exception { this.setComplete(); this.endTransaction(); diff --git a/source/java/org/alfresco/repo/security/authentication/AuthenticationServiceImpl.java b/source/java/org/alfresco/repo/security/authentication/AuthenticationServiceImpl.java index bdc662b000..55edbb515b 100644 --- a/source/java/org/alfresco/repo/security/authentication/AuthenticationServiceImpl.java +++ b/source/java/org/alfresco/repo/security/authentication/AuthenticationServiceImpl.java @@ -23,8 +23,8 @@ import java.util.Set; import org.alfresco.repo.management.subsystems.ActivateableBean; import org.alfresco.repo.security.authentication.AuthenticationComponent.UserNameValidationMode; -import org.alfresco.repo.tenant.TenantService; -import org.alfresco.repo.tenant.TenantUtil; +import org.alfresco.repo.tenant.TenantContextHolder; +import org.alfresco.util.Pair; public class AuthenticationServiceImpl extends AbstractAuthenticationService implements ActivateableBean { @@ -62,7 +62,7 @@ public class AuthenticationServiceImpl extends AbstractAuthenticationService imp try { // clear context - to avoid MT concurrency issue (causing domain mismatch) - see also 'validate' below - clearCurrentSecurityContext(); + //clearCurrentSecurityContext(); preAuthenticationCheck(userName); authenticationComponent.authenticate(userName, password); } @@ -111,11 +111,20 @@ public class AuthenticationServiceImpl extends AbstractAuthenticationService imp String currentUser = null; try { - + String tenant = TenantContextHolder.getTenantDomain(); + // clear context - to avoid MT concurrency issue (causing domain mismatch) - see also 'authenticate' above clearCurrentSecurityContext(); currentUser = ticketComponent.validateTicket(ticket); authenticationComponent.setCurrentUser(currentUser, UserNameValidationMode.NONE); + + if (tenant == null) + { + Pair userTenant = AuthenticationUtil.getUserTenant(currentUser); + tenant = userTenant.getSecond(); + } + + TenantContextHolder.setTenantDomain(tenant); } catch (AuthenticationException ae) { diff --git a/source/java/org/alfresco/repo/security/authentication/AuthenticationTest.java b/source/java/org/alfresco/repo/security/authentication/AuthenticationTest.java index 8f1b470ebd..f35af0f6e8 100644 --- a/source/java/org/alfresco/repo/security/authentication/AuthenticationTest.java +++ b/source/java/org/alfresco/repo/security/authentication/AuthenticationTest.java @@ -39,6 +39,7 @@ import net.sf.acegisecurity.DisabledException; import net.sf.acegisecurity.LockedException; import net.sf.acegisecurity.UserDetails; import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken; +import net.sf.acegisecurity.providers.encoding.PasswordEncoder; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; @@ -79,6 +80,7 @@ public class AuthenticationTest extends TestCase private AuthorityService authorityService; private TenantService tenantService; private MD4PasswordEncoder passwordEncoder; + private PasswordEncoder sha256PasswordEncoder; private MutableAuthenticationDao dao; private AuthenticationManager authenticationManager; private TicketComponent ticketComponent; @@ -130,6 +132,7 @@ public class AuthenticationTest extends TestCase authorityService = (AuthorityService) ctx.getBean("authorityService"); tenantService = (TenantService) ctx.getBean("tenantService"); passwordEncoder = (MD4PasswordEncoder) ctx.getBean("passwordEncoder"); + sha256PasswordEncoder = (PasswordEncoder) ctx.getBean("sha256PasswordEncoder"); ticketComponent = (TicketComponent) ctx.getBean("ticketComponent"); authenticationService = (MutableAuthenticationService) ctx.getBean("authenticationService"); pubAuthenticationService = (MutableAuthenticationService) ctx.getBean("AuthenticationService"); @@ -183,6 +186,7 @@ public class AuthenticationTest extends TestCase dao.setNodeService(nodeService); dao.setNamespaceService(getNamespacePrefixReolsver("")); dao.setPasswordEncoder(passwordEncoder); + dao.setSha256PasswordEncoder(sha256PasswordEncoder); dao.setPolicyComponent(policyComponent); dao.setAuthenticationCache(authenticationCache); dao.setSingletonCache(immutableSingletonCache); @@ -401,6 +405,7 @@ public class AuthenticationTest extends TestCase dao.setAuthorityService(authorityService); dao.setNamespaceService(getNamespacePrefixReolsver("")); dao.setPasswordEncoder(passwordEncoder); + dao.setSha256PasswordEncoder(sha256PasswordEncoder); dao.setPolicyComponent(policyComponent); dao.setAuthenticationCache(authenticationCache); dao.setSingletonCache(immutableSingletonCache); diff --git a/source/java/org/alfresco/repo/security/authentication/MessageDigestPasswordEncoder.java b/source/java/org/alfresco/repo/security/authentication/MessageDigestPasswordEncoder.java new file mode 100644 index 0000000000..5db4b93c07 --- /dev/null +++ b/source/java/org/alfresco/repo/security/authentication/MessageDigestPasswordEncoder.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2005-2013 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.security.authentication; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +import net.sf.acegisecurity.providers.encoding.BaseDigestPasswordEncoder; + +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.codec.binary.Hex; + +/** + * Base for digest password encoders. + *

+ * This class can be used stand-alone, or one of the subclasses can be used for + * compatiblity and convenience. When using this class directly you must specify + * a + * Message Digest Algorithm to use as a constructor arg + *

+ * + *

+ * The encoded password hash is normally returned as Hex (32 char) version of + * the hash bytes. Setting the encodeHashAsBase64 property to + * true will cause the encoded pass to be returned as Base64 text, + * which will consume 24 characters. See + * {@link BaseDigestPasswordEncoder#setEncodeHashAsBase64(boolean)} + *

+ *

+ * This PasswordEncoder can be used directly as in the following example:
+ * + *

+ * <bean id="passwordEncoder" class="org.springframework.security.authentication.encoding.MessageDigestPasswordEncoder">
+ *     <constructor-arg value="MD5"/>
+ * </bean>
+ * 
+ * + *

+ */ +public class MessageDigestPasswordEncoder extends BaseDigestPasswordEncoder +{ + + private final String algorithm; + + /** + * The digest algorithm to use Supports the named Message Digest Algorithms in the Java environment. + * + * @param algorithm + */ + public MessageDigestPasswordEncoder(String algorithm) + { + this(algorithm, false); + } + + /** + * Convenience constructor for specifying the algorithm and whether or not + * to enable base64 encoding + * + * @param algorithm + * @param encodeHashAsBase64 + * @throws IllegalArgumentException + * if an unknown + */ + public MessageDigestPasswordEncoder(String algorithm, boolean encodeHashAsBase64) throws IllegalArgumentException + { + this.algorithm = algorithm; + setEncodeHashAsBase64(encodeHashAsBase64); + // Validity Check + getMessageDigest(); + } + + /** + * Encodes the rawPass using a MessageDigest. If a salt is specified it will + * be merged with the password before encoding. + * + * @param rawPass + * The plain text password + * @param salt + * The salt to sprinkle + * @return Hex string of password digest (or base64 encoded string if + * encodeHashAsBase64 is enabled. + */ + public String encodePassword(String rawPass, Object salt) + { + String saltedPass = mergePasswordAndSalt(rawPass, salt, false); + + MessageDigest messageDigest = getMessageDigest(); + + byte[] digest; + + try + { + digest = messageDigest.digest(saltedPass.getBytes("UTF-8")); + } catch (UnsupportedEncodingException e) + { + throw new IllegalStateException("UTF-8 not supported!"); + } + + if (getEncodeHashAsBase64()) + { + return new String(Base64.encodeBase64(digest)); + } else + { + return new String(Hex.encodeHex(digest)); + } + } + + /** + * Get a MessageDigest instance for the given algorithm. Throws an + * IllegalArgumentException if algorithm is unknown + * + * @return MessageDigest instance + * @throws IllegalArgumentException + * if NoSuchAlgorithmException is thrown + */ + protected final MessageDigest getMessageDigest() throws IllegalArgumentException + { + try + { + return MessageDigest.getInstance(algorithm); + } catch (NoSuchAlgorithmException e) + { + throw new IllegalArgumentException("No such algorithm [" + algorithm + "]"); + } + } + + /** + * Takes a previously encoded password and compares it with a rawpassword + * after mixing in the salt and encoding that value + * + * @param encPass + * previously encoded password + * @param rawPass + * plain text password + * @param salt + * salt to mix into password + * @return true or false + */ + public boolean isPasswordValid(String encPass, String rawPass, Object salt) + { + String pass1 = "" + encPass; + String pass2 = encodePassword(rawPass, salt); + + return pass1.equals(pass2); + } + + public String getAlgorithm() + { + return algorithm; + } +} diff --git a/source/java/org/alfresco/repo/security/authentication/RepositoryAuthenticationDao.java b/source/java/org/alfresco/repo/security/authentication/RepositoryAuthenticationDao.java index f80cbcd668..196b1aa7ab 100644 --- a/source/java/org/alfresco/repo/security/authentication/RepositoryAuthenticationDao.java +++ b/source/java/org/alfresco/repo/security/authentication/RepositoryAuthenticationDao.java @@ -52,6 +52,7 @@ import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.RegexQNamePattern; import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.EqualsHelper; +import org.alfresco.util.GUID; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.InitializingBean; @@ -68,13 +69,13 @@ public class RepositoryAuthenticationDao implements MutableAuthenticationDao, In private static final Log logger = LogFactory.getLog(RepositoryAuthenticationDao.class); - private AuthorityService authorityService; - - private NodeService nodeService; - private TenantService tenantService; - private NamespacePrefixResolver namespacePrefixResolver; - private PasswordEncoder passwordEncoder; - private PolicyComponent policyComponent; + protected AuthorityService authorityService; + protected NodeService nodeService; + protected TenantService tenantService; + protected NamespacePrefixResolver namespacePrefixResolver; + protected PasswordEncoder passwordEncoder; + protected PasswordEncoder sha256PasswordEncoder; + protected PolicyComponent policyComponent; private TransactionService transactionService; @@ -120,6 +121,11 @@ public class RepositoryAuthenticationDao implements MutableAuthenticationDao, In this.passwordEncoder = passwordEncoder; } + public void setSha256PasswordEncoder(PasswordEncoder passwordEncoder) + { + this.sha256PasswordEncoder = passwordEncoder; + } + public void setPolicyComponent(PolicyComponent policyComponent) { this.policyComponent = policyComponent; @@ -199,10 +205,22 @@ public class RepositoryAuthenticationDao implements MutableAuthenticationDao, In GrantedAuthority[] gas = new GrantedAuthority[1]; gas[0] = new GrantedAuthorityImpl("ROLE_AUTHENTICATED"); - - return new CacheEntry(userRef, new User(userName, password, getEnabled(userName, properties), - !getHasExpired(userName, properties), true, !getLocked(userName, properties), gas), - getCredentialsExpiryDate(userName, properties)); + + boolean isAdminAuthority = authorityService.isAdminAuthority(userName); + + Date credentialsExpiryDate = getCredentialsExpiryDate(userName, properties, isAdminAuthority); + boolean credentialsHaveNotExpired = (credentialsExpiryDate == null || credentialsExpiryDate.compareTo(new Date()) >= 0); + + UserDetails ud = new User( + userName, + password, + getEnabled(userName, properties, isAdminAuthority), + !getHasExpired(userName, properties, isAdminAuthority), + credentialsHaveNotExpired, + !getLocked(userName, properties, isAdminAuthority), + gas); + + return new CacheEntry(userRef, ud, credentialsExpiryDate); } return null; } @@ -252,9 +270,10 @@ public class RepositoryAuthenticationDao implements MutableAuthenticationDao, In NodeRef typesNode = getUserFolderLocation(caseSensitiveUserName); Map properties = new HashMap(); properties.put(ContentModel.PROP_USER_USERNAME, caseSensitiveUserName); - String salt = null; // GUID.generate(); + String salt = GUID.generate(); properties.put(ContentModel.PROP_SALT, salt); - properties.put(ContentModel.PROP_PASSWORD, passwordEncoder.encodePassword(new String(rawPassword), salt)); + properties.put(ContentModel.PROP_PASSWORD, passwordEncoder.encodePassword(new String(rawPassword), null)); + properties.put(ContentModel.PROP_PASSWORD_SHA256, sha256PasswordEncoder.encodePassword(new String(rawPassword), salt)); properties.put(ContentModel.PROP_ACCOUNT_EXPIRES, Boolean.valueOf(false)); properties.put(ContentModel.PROP_CREDENTIALS_EXPIRE, Boolean.valueOf(false)); properties.put(ContentModel.PROP_ENABLED, Boolean.valueOf(true)); @@ -308,11 +327,13 @@ public class RepositoryAuthenticationDao implements MutableAuthenticationDao, In throw new AuthenticationException("User name does not exist: " + userName); } Map properties = nodeService.getProperties(userRef); - String salt = null; // GUID.generate(); + String salt = GUID.generate(); properties.remove(ContentModel.PROP_SALT); properties.put(ContentModel.PROP_SALT, salt); properties.remove(ContentModel.PROP_PASSWORD); - properties.put(ContentModel.PROP_PASSWORD, passwordEncoder.encodePassword(new String(rawPassword), salt)); + properties.put(ContentModel.PROP_PASSWORD, passwordEncoder.encodePassword(new String(rawPassword), null)); + properties.remove(ContentModel.PROP_PASSWORD_SHA256); + properties.put(ContentModel.PROP_PASSWORD_SHA256, sha256PasswordEncoder.encodePassword(new String(rawPassword), salt)); nodeService.setProperties(userRef, properties); } @@ -342,7 +363,7 @@ public class RepositoryAuthenticationDao implements MutableAuthenticationDao, In /** * @return Returns the user properties or null if there are none */ - private Map getUserProperties(String userName) + protected Map getUserProperties(String userName) { NodeRef userNodeRef = getUserOrNull(userName); if (userNodeRef == null) @@ -396,16 +417,20 @@ public class RepositoryAuthenticationDao implements MutableAuthenticationDao, In @Override public boolean getAccountHasExpired(String userName) { - return getHasExpired(userName, null); + return getHasExpired(userName, null, null); } /** * @param userName the username * @param properties user properties or null to fetch them */ - private boolean getHasExpired(String userName, Map properties) + protected boolean getHasExpired(String userName, Map properties, Boolean isAdminAuthority) { - if (authorityService.isAdminAuthority(userName)) + if (isAdminAuthority == null) + { + isAdminAuthority = authorityService.isAdminAuthority(userName); + } + if (isAdminAuthority) { return false; // Admin never expires } @@ -434,26 +459,30 @@ public class RepositoryAuthenticationDao implements MutableAuthenticationDao, In return false; } } - + @Override public boolean getLocked(String userName) { - return getLocked(userName, null); + return getLocked(userName, null, null); } - + @Override public boolean getAccountlocked(String userName) { - return getLocked(userName, null); + return getLocked(userName, null, null); } - + /** * @param userName the username * @param properties user properties or null to fetch them */ - private boolean getLocked(String userName, Map properties) + protected boolean getLocked(String userName, Map properties, Boolean isAdminAuthority) { - if (authorityService.isAdminAuthority(userName)) + if (isAdminAuthority == null) + { + isAdminAuthority = authorityService.isAdminAuthority(userName); + } + if (isAdminAuthority) { return false; // Admin is never locked } @@ -486,7 +515,7 @@ public class RepositoryAuthenticationDao implements MutableAuthenticationDao, In * @param userName the username * @param properties user properties or null to fetch them */ - private boolean getCredentialsExpire(String userName, Map properties) + protected boolean getCredentialsExpire(String userName, Map properties) { if (authorityService.isAdminAuthority(userName)) { @@ -534,15 +563,20 @@ public class RepositoryAuthenticationDao implements MutableAuthenticationDao, In { return !loadUserByUsername(userName).isCredentialsNonExpired(); } - + /** * @param userName the username (never null * @param properties the properties associated with the user or null to get them + * @param isAdminAuthority is admin authority * @return Date on which the credentials expire or null if they never expire */ - private Date getCredentialsExpiryDate(String userName, Map properties) + private Date getCredentialsExpiryDate(String userName, Map properties, Boolean isAdminAuthority) { - if (authorityService.isAdminAuthority(userName)) + if (isAdminAuthority == null) + { + isAdminAuthority = authorityService.isAdminAuthority(userName); + } + if (isAdminAuthority) { return null; // Admin never expires } @@ -550,33 +584,30 @@ public class RepositoryAuthenticationDao implements MutableAuthenticationDao, In { properties = getUserProperties(userName); } - if (properties == null) - { - return null; - } if (DefaultTypeConverter.INSTANCE.booleanValue(properties.get(ContentModel.PROP_CREDENTIALS_EXPIRE))) { return DefaultTypeConverter.INSTANCE.convert(Date.class, properties.get(ContentModel.PROP_CREDENTIALS_EXPIRY_DATE)); - } - else - { - return null; } + return null; } - + @Override public boolean getEnabled(String userName) { - return getEnabled(userName, null); + return getEnabled(userName, null, null); } - + /** * @param userName the username * @param properties the user's properties or null */ - private boolean getEnabled(String userName, Map properties) + protected boolean getEnabled(String userName, Map properties, Boolean isAdminAuthority) { - if (authorityService.isAdminAuthority(userName)) + if (isAdminAuthority == null) + { + isAdminAuthority = authorityService.isAdminAuthority(userName); + } + if (isAdminAuthority) { return true; // Admin is always enabled } diff --git a/source/java/org/alfresco/repo/security/authentication/ShaPasswordEncoderImpl.java b/source/java/org/alfresco/repo/security/authentication/ShaPasswordEncoderImpl.java new file mode 100644 index 0000000000..c024861014 --- /dev/null +++ b/source/java/org/alfresco/repo/security/authentication/ShaPasswordEncoderImpl.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2005-2013 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.security.authentication; + +/** + *

+ * SHA implementation of PasswordEncoder. + *

+ *

+ * If a null password is presented, it will be treated as an empty + * String ("") password. + *

+ *

+ * As SHA is a one-way hash, the salt can contain any characters. The default + * strength for the SHA encoding is SHA-1. If you wish to use higher strengths + * use the argumented constructor. {@link #ShaPasswordEncoder(int strength)} + *

+ *

+ * The applicationContext example... + * + *

+ * <bean id="passwordEncoder" class="org.springframework.security.authentication.encoding.ShaPasswordEncoder">
+ *     <constructor-arg value="256"/>
+ * </bean>
+ * 
+ */ +public class ShaPasswordEncoderImpl extends MessageDigestPasswordEncoder +{ + + /** + * Initializes the ShaPasswordEncoder for SHA-1 strength + */ + public ShaPasswordEncoderImpl() + { + this(1); + } + + /** + * Initialize the ShaPasswordEncoder with a given SHA stength as supported + * by the JVM EX: + * ShaPasswordEncoder encoder = new ShaPasswordEncoder(256); + * initializes with SHA-256 + * + * @param strength + * EX: 1, 256, 384, 512 + */ + public ShaPasswordEncoderImpl(int strength) + { + super("SHA-" + strength); + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/security/authentication/userModel.xml b/source/java/org/alfresco/repo/security/authentication/userModel.xml index f6ac271b58..c1923f2e00 100644 --- a/source/java/org/alfresco/repo/security/authentication/userModel.xml +++ b/source/java/org/alfresco/repo/security/authentication/userModel.xml @@ -40,6 +40,9 @@ d:text + + d:text + d:boolean diff --git a/source/java/org/alfresco/repo/security/authority/script/ScriptAuthorityService.java b/source/java/org/alfresco/repo/security/authority/script/ScriptAuthorityService.java index 83a732464f..31e3165e2d 100644 --- a/source/java/org/alfresco/repo/security/authority/script/ScriptAuthorityService.java +++ b/source/java/org/alfresco/repo/security/authority/script/ScriptAuthorityService.java @@ -486,6 +486,8 @@ public class ScriptAuthorityService extends BaseScopableProcessorExtension * @param paging Paging object with max number to return, and items to skip * @param sortBy What to sort on (firstName, lastName or userName) * @return the users matching the query + * + * @deprecated see People.getPeople(String filter, ScriptPagingDetails pagingRequest, String sortBy) */ public ScriptUser[] searchUsers(String nameFilter, ScriptPagingDetails paging, String sortBy) { diff --git a/source/java/org/alfresco/repo/security/person/GetPeopleCannedQueryFactory.java b/source/java/org/alfresco/repo/security/person/GetPeopleCannedQueryFactory.java index 5e1ab4822e..8e1282e760 100644 --- a/source/java/org/alfresco/repo/security/person/GetPeopleCannedQueryFactory.java +++ b/source/java/org/alfresco/repo/security/person/GetPeopleCannedQueryFactory.java @@ -97,7 +97,8 @@ public class GetPeopleCannedQueryFactory extends AbstractCannedQueryFactory> sortPairs = new ArrayList>(sortProps.size()); for (Pair sortProp : sortProps) { - sortPairs.add(new Pair(sortProp.getFirst(), (sortProp.getSecond() ? SortOrder.ASCENDING : SortOrder.DESCENDING))); + boolean sortAsc = ((sortProp.getSecond() == null) || sortProp.getSecond()); + sortPairs.add(new Pair(sortProp.getFirst(), (sortAsc ? SortOrder.ASCENDING : SortOrder.DESCENDING))); } cqsd = new CannedQuerySortDetails(sortPairs); diff --git a/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java b/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java index 64fc45e021..8aa20b1ce4 100644 --- a/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java +++ b/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java @@ -53,8 +53,6 @@ import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.security.permissions.PermissionServiceSPI; import org.alfresco.repo.tenant.TenantDomainMismatchException; import org.alfresco.repo.tenant.TenantService; -import org.alfresco.repo.tenant.TenantUtil; -import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; @@ -80,10 +78,12 @@ import org.alfresco.service.cmr.search.LimitBy; import org.alfresco.service.cmr.search.ResultSet; import org.alfresco.service.cmr.search.SearchParameters; import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.cmr.security.AccessStatus; import org.alfresco.service.cmr.security.AuthorityService; import org.alfresco.service.cmr.security.AuthorityType; import org.alfresco.service.cmr.security.MutableAuthenticationService; import org.alfresco.service.cmr.security.NoSuchPersonException; +import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.namespace.NamespacePrefixResolver; import org.alfresco.service.namespace.NamespaceService; @@ -165,7 +165,9 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per private JavaBehaviour beforeDeleteNodeValidationBehaviour; private boolean homeFolderCreationEager; - + + private boolean homeFolderCreationDisabled = false; // if true then home folders creation is disabled (ie. home folders are not created - neither eagerly nor lazily) + static { Set props = new HashSet(); @@ -363,6 +365,14 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per this.homeFolderCreationEager = homeFolderCreationEager; } + /** + * Indicates if home folder creation should be disabled. + */ + public void setHomeFolderCreationDisabled(boolean homeFolderCreationDisabled) + { + this.homeFolderCreationDisabled = homeFolderCreationDisabled; + } + public void setAclDAO(AclDAO aclDao) { this.aclDao = aclDao; @@ -426,7 +436,35 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per */ public NodeRef getPersonOrNull(String userName) { - return getPerson(userName, false, false); + return getPersonImpl(userName, false, false); + } + + /** + * {@inheritDoc} + */ + public PersonInfo getPerson(NodeRef personRef) throws NoSuchPersonException + { + Map props = null; + try + { + props = nodeService.getProperties(personRef); + } + catch (InvalidNodeRefException inre) + { + throw new NoSuchPersonException(personRef.toString()); + } + + // belts-and-braces + String username = (String)props.get(ContentModel.PROP_USERNAME); + if (getPersonOrNull(username) == null) + { + throw new NoSuchPersonException(personRef.toString()); + } + + return new PersonInfo(personRef, + username, + (String)props.get(ContentModel.PROP_FIRSTNAME), + (String)props.get(ContentModel.PROP_LASTNAME)); } /** @@ -434,31 +472,7 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per */ public NodeRef getPerson(final String userName, final boolean autoCreateHomeFolderAndMissingPersonIfAllowed) { - return getPerson(userName, autoCreateHomeFolderAndMissingPersonIfAllowed, true); - } - - private NodeRef getPerson( - final String userName, - final boolean autoCreateHomeFolderAndMissingPersonIfAllowed, - final boolean exceptionOrNull) - { - // MT share - for activity service system callback - if (tenantService.isEnabled() && (AuthenticationUtil.SYSTEM_USER_NAME.equals(AuthenticationUtil.getRunAsUser())) && tenantService.isTenantUser(userName)) - { - final String tenantDomain = tenantService.getUserDomain(userName); - - return TenantUtil.runAsSystemTenant(new TenantRunAsWork() - { - public NodeRef doWork() throws Exception - { - return getPersonImpl(userName, autoCreateHomeFolderAndMissingPersonIfAllowed, exceptionOrNull); - } - }, tenantDomain); - } - else - { - return getPersonImpl(userName, autoCreateHomeFolderAndMissingPersonIfAllowed, exceptionOrNull); - } + return getPersonImpl(userName, autoCreateHomeFolderAndMissingPersonIfAllowed, true); } private NodeRef getPersonImpl( @@ -500,7 +514,13 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per */ public boolean personExists(String caseSensitiveUserName) { - return getPersonOrNullImpl(caseSensitiveUserName) != null; + NodeRef person = getPersonOrNull(caseSensitiveUserName); + if (person != null) + { + // re: THOR-293 + return permissionServiceSPI.hasPermission(person, PermissionService.READ) == AccessStatus.ALLOWED; + } + return false; } private NodeRef getPersonOrNullImpl(String searchUserName) @@ -840,7 +860,7 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per private void makeHomeFolderIfRequired(NodeRef person) { - if (person != null) + if ((person != null) && (homeFolderCreationDisabled == false)) { NodeRef homeFolder = DefaultTypeConverter.INSTANCE.convert(NodeRef.class, nodeService.getProperty(person, ContentModel.PROP_HOMEFOLDER)); if (homeFolder == null) @@ -1701,7 +1721,7 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per // Make sure there is an authority entry - with a DB constraint for uniqueness // aclDao.createAuthority(username); - if (homeFolderCreationEager) + if ((homeFolderCreationEager) && (homeFolderCreationDisabled == false)) { makeHomeFolderAsSystem(childAssocRef); } diff --git a/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizerTest.java b/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizerTest.java index ae5d5545ff..815a48b97e 100644 --- a/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizerTest.java +++ b/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2012 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -39,6 +39,8 @@ import junit.framework.TestCase; import org.alfresco.model.ContentModel; import org.alfresco.repo.management.subsystems.ChildApplicationContextManager; import org.alfresco.repo.security.authentication.AuthenticationContext; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.security.person.PersonServiceImpl; import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; @@ -115,6 +117,10 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase this.authenticationContext = (AuthenticationContext) ChainingUserRegistrySynchronizerTest.context .getBean("authenticationContext"); + + + // this.authenticationContext.setSystemUserAsCurrentUser(); + //AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); this.authenticationContext.setSystemUserAsCurrentUser(); this.retryingTransactionHelper = (RetryingTransactionHelper) ChainingUserRegistrySynchronizerTest.context @@ -298,8 +304,16 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase public Object execute() throws Throwable { - - ChainingUserRegistrySynchronizerTest.this.synchronizer.synchronize(false, false, false); + // re: THOR-293 - note: use runAs else security context is cleared (=> current system user becomes null and personExists fails) + AuthenticationUtil.runAsSystem(new RunAsWork() + { + public Void doWork() throws Exception + { + ChainingUserRegistrySynchronizerTest.this.synchronizer.synchronize(false, false, false); + return null; + } + }); + // Stay in the same transaction assertExists("Z1", "U1"); assertEmailEquals("U1", "changeofemail@alfresco.com"); diff --git a/source/java/org/alfresco/repo/site/SitesPermissionCleaner.java b/source/java/org/alfresco/repo/site/SitesPermissionCleaner.java index 047ea155d4..5863763b5c 100644 --- a/source/java/org/alfresco/repo/site/SitesPermissionCleaner.java +++ b/source/java/org/alfresco/repo/site/SitesPermissionCleaner.java @@ -92,7 +92,7 @@ public class SitesPermissionCleaner public void cleanSitePermissions(final NodeRef targetNode, SiteInfo containingSite) { - if (nodeService.exists(targetNode)) + if (nodeDAO.exists(targetNode)) { // We can calculate the containing site at the start of a recursive call & then reuse it on subsequent calls. if (containingSite == null) diff --git a/source/java/org/alfresco/repo/subscriptions/SubscriptionServiceImpl.java b/source/java/org/alfresco/repo/subscriptions/SubscriptionServiceImpl.java index 3040ee482b..40d7d88832 100644 --- a/source/java/org/alfresco/repo/subscriptions/SubscriptionServiceImpl.java +++ b/source/java/org/alfresco/repo/subscriptions/SubscriptionServiceImpl.java @@ -28,10 +28,12 @@ import org.alfresco.model.ContentModel; import org.alfresco.query.PagingRequest; import org.alfresco.repo.action.executer.MailActionExecuter; import org.alfresco.repo.activities.ActivityType; +import org.alfresco.repo.dictionary.RepositoryLocation; import org.alfresco.repo.domain.subscriptions.SubscriptionsDAO; import org.alfresco.repo.search.SearcherException; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.permissions.AccessDeniedException; +import org.alfresco.repo.tenant.TenantUtil; import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.ActionService; import org.alfresco.service.cmr.activities.ActivityService; @@ -73,12 +75,14 @@ public class SubscriptionServiceImpl implements SubscriptionService private static final String USER_USERNAME = "userUserName"; private static final String FOLLOWING_COUNT = "followingCount"; private static final String FOLLOWER_COUNT = "followerCount"; - + private static final String SUBSCRIBER_FIRSTNAME = "subscriberFirstName"; private static final String SUBSCRIBER_LASTNAME = "subscriberLastName"; private static final String SUBSCRIBER_USERNAME = "subscriberUserName"; private static final String NODE = "node"; - + + private static final String TENANT_DOMAIN = "tenantDomain"; + protected SubscriptionsDAO subscriptionsDAO; protected NodeService nodeService; protected PersonService personService; @@ -90,6 +94,8 @@ public class SubscriptionServiceImpl implements SubscriptionService protected FileFolderService fileFolderService; protected boolean active; + + private RepositoryLocation followingEmailTemplateLocation; /** * Sets the subscriptions DAO. @@ -167,7 +173,13 @@ public class SubscriptionServiceImpl implements SubscriptionService { this.active = active; } - + + public void setFollowingEmailTemplateLocation(RepositoryLocation followingEmailTemplateLocation) + { + this.followingEmailTemplateLocation = followingEmailTemplateLocation; + } + + @Override public PagingSubscriptionResults getSubscriptions(String userId, SubscriptionItemTypeEnum type, PagingRequest pagingRequest) @@ -472,7 +484,7 @@ public class SubscriptionServiceImpl implements SubscriptionService return; } - NodeRef templateNodeRef = getEmailTemplateRef(); + String templateNodeRef = getEmailTemplateRef(); if (templateNodeRef == null) { // we can't send an email without template @@ -507,9 +519,16 @@ public class SubscriptionServiceImpl implements SubscriptionService } catch (Exception e) { } - + + // Add tenant, if in context of tenant + String tenantDomain = TenantUtil.getCurrentDomain(); + if (tenantDomain != null) + { + model.put(TENANT_DOMAIN, tenantDomain); + } + Action mail = actionService.createAction(MailActionExecuter.NAME); - + mail.setParameterValue(MailActionExecuter.PARAM_TO, emailAddress); mail.setParameterValue(MailActionExecuter.PARAM_SUBJECT, subjectText); mail.setParameterValue(MailActionExecuter.PARAM_TEMPLATE, templateNodeRef); @@ -523,32 +542,48 @@ public class SubscriptionServiceImpl implements SubscriptionService * Returns the NodeRef of the email template or null if the * template coudln't be found. */ - protected NodeRef getEmailTemplateRef() + protected String getEmailTemplateRef() { - // Find the following email template - String xpath = "app:company_home/app:dictionary/app:email_templates/app:following/cm:following-email.html.ftl"; - try + String locationType = followingEmailTemplateLocation.getQueryLanguage(); + + if (locationType.equals(SearchService.LANGUAGE_XPATH)) { - NodeRef rootNodeRef = nodeService.getRootNode(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE); - List nodeRefs = searchService.selectNodes(rootNodeRef, xpath, null, namespaceService, false); - if (nodeRefs.size() > 1) + // Find the following email template + StoreRef store = followingEmailTemplateLocation.getStoreRef(); + String xpath = followingEmailTemplateLocation.getPath(); + + try { - logger.error("Found too many email templates using: " + xpath); - nodeRefs = Collections.singletonList(nodeRefs.get(0)); - } else if (nodeRefs.size() == 0) + NodeRef rootNodeRef = nodeService.getRootNode(store); + List nodeRefs = searchService.selectNodes(rootNodeRef, xpath, null, namespaceService, false); + if (nodeRefs.size() > 1) + { + logger.error("Found too many email templates using: " + xpath); + nodeRefs = Collections.singletonList(nodeRefs.get(0)); + } + else if (nodeRefs.size() == 0) + { + logger.error("Cannot find the email template using " + xpath); + return null; + } + // Now localise this + return fileFolderService.getLocalizedSibling(nodeRefs.get(0)).toString(); + } + catch (SearcherException e) { - logger.error("Cannot find the email template using " + xpath); - return null; + logger.error("Cannot find the email template!", e); } - // Now localise this - NodeRef base = nodeRefs.get(0); - NodeRef local = fileFolderService.getLocalizedSibling(base); - return local; - } catch (SearcherException e) - { - logger.error("Cannot find the email template!", e); + + return null; + } + else if (locationType.equals(RepositoryLocation.LANGUAGE_CLASSPATH)) + { + return followingEmailTemplateLocation.getPath(); + } + else + { + logger.error("Unsupported location type: "+locationType); + return null; } - - return null; } } diff --git a/source/java/org/alfresco/repo/tenant/AbstractTenantRoutingContentStore.java b/source/java/org/alfresco/repo/tenant/AbstractTenantRoutingContentStore.java index 6b3dcf7265..9abe8b30c4 100644 --- a/source/java/org/alfresco/repo/tenant/AbstractTenantRoutingContentStore.java +++ b/source/java/org/alfresco/repo/tenant/AbstractTenantRoutingContentStore.java @@ -19,7 +19,6 @@ package org.alfresco.repo.tenant; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; @@ -33,6 +32,8 @@ import org.alfresco.repo.domain.tenant.TenantAdminDAO; import org.alfresco.repo.domain.tenant.TenantEntity; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; @@ -44,6 +45,8 @@ import org.springframework.context.ApplicationContextAware; */ public abstract class AbstractTenantRoutingContentStore extends AbstractRoutingContentStore implements ApplicationContextAware, TenantRoutingContentStore { + private static Log logger = LogFactory.getLog(AbstractTenantRoutingContentStore.class); + private String defaultRootDirectory; private TenantAdminDAO tenantAdminDAO; protected TenantService tenantService; @@ -102,14 +105,14 @@ public abstract class AbstractTenantRoutingContentStore extends AbstractRoutingC @Override public List getAllStores() { + final List allEnabledStores = new ArrayList(); + if (tenantService.isEnabled()) { String currentUser = AuthenticationUtil.getFullyAuthenticatedUser(); if ((currentUser == null) || (tenantService.getBaseNameUser(currentUser).equals(AuthenticationUtil.getSystemUserName()))) { // return enabled stores across all tenants, if running as system/null user, for example, ContentStoreCleaner scheduled job - final List allEnabledStores = new ArrayList(); - List tenants = tenantAdminDAO.listTenants(); for (TenantEntity tenant : tenants) { @@ -127,16 +130,18 @@ public abstract class AbstractTenantRoutingContentStore extends AbstractRoutingC } } - if (allEnabledStores.size() > 0) + if (logger.isDebugEnabled()) { - allEnabledStores.add(getTenantContentStore()); - return allEnabledStores; + logger.debug("getAllStores called without tenant ctx ("+tenants.size()+" tenants)"); } // drop through to ensure default content store has been init'ed } } - return Arrays.asList(getTenantContentStore()); + + allEnabledStores.add(getTenantContentStore()); + + return allEnabledStores; } private ContentStore getTenantContentStore() diff --git a/source/java/org/alfresco/repo/tenant/MultiTAdminServiceImpl.java b/source/java/org/alfresco/repo/tenant/MultiTAdminServiceImpl.java index e9ce9e0846..edf8f3a546 100644 --- a/source/java/org/alfresco/repo/tenant/MultiTAdminServiceImpl.java +++ b/source/java/org/alfresco/repo/tenant/MultiTAdminServiceImpl.java @@ -307,17 +307,27 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo throw new AlfrescoRuntimeException("MT: cannot start tenants - TenantRoutingContentStore is not configured AND not all tenants use co-mingled content store"); } + String tenantDomain = tenant.getTenantDomain(); + if (tenant.isEnabled()) { - // this will also call tenant deployers registered so far ... - enableTenant(tenant.getTenantDomain(), true); + // notify tenant deployers registered so far ... + notifyAfterEnableTenant(tenantDomain); enabledCount++; + + if (logger.isDebugEnabled()) + { + logger.debug("Tenant enabled: " + tenantDomain); + } } else { - // explicitly disable, without calling disableTenant callback - disableTenant(tenant.getTenantDomain(), false); disabledCount++; + + if (logger.isDebugEnabled()) + { + logger.debug("Tenant disabled: " + tenantDomain); + } } } @@ -377,7 +387,7 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo /** * @see TenantAdminService.createTenant() */ - public void createTenant(final String tenantDomainIn, final char[] tenantAdminRawPassword, String contentRootPath, String dbUrl) + public void createTenant(final String tenantDomainIn, final char[] tenantAdminRawPassword, String contentRootPath, final String dbUrl) { ParameterCheck.mandatory("tenantAdminRawPassword", tenantAdminRawPassword); @@ -429,65 +439,72 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo AuthenticationUtil.pushAuthentication(); AuthenticationUtil.setFullyAuthenticatedUser(getSystemUser(tenantDomain)); - dictionaryComponent.init(); - - if (tenantFileContentStore instanceof TenantDeployer) + TenantUtil.runAsSystemTenant(new TenantRunAsWork() { - ((TenantDeployer)tenantFileContentStore).init(); - } - - // callback - RetryingTransactionCallback doImportCallback = new RetryingTransactionCallback() - { - public Object execute() throws Throwable + public Object doWork() { - // create tenant-specific stores - ImporterBootstrap userImporterBootstrap = (ImporterBootstrap)ctx.getBean("userBootstrap-mt"); - bootstrapUserTenantStore(userImporterBootstrap, tenantDomain, tenantAdminRawPassword); + dictionaryComponent.init(); - ImporterBootstrap systemImporterBootstrap = (ImporterBootstrap)ctx.getBean("systemBootstrap-mt"); - bootstrapSystemTenantStore(systemImporterBootstrap, tenantDomain); - - // deprecated - ImporterBootstrap versionImporterBootstrap = (ImporterBootstrap)ctx.getBean("versionBootstrap-mt"); - bootstrapVersionTenantStore(versionImporterBootstrap, tenantDomain); - - ImporterBootstrap version2ImporterBootstrap = (ImporterBootstrap)ctx.getBean("version2Bootstrap-mt"); - bootstrapVersionTenantStore(version2ImporterBootstrap, tenantDomain); - - ImporterBootstrap spacesArchiveImporterBootstrap = (ImporterBootstrap)ctx.getBean("spacesArchiveBootstrap-mt"); - bootstrapSpacesArchiveTenantStore(spacesArchiveImporterBootstrap, tenantDomain); - - ImporterBootstrap spacesImporterBootstrap = (ImporterBootstrap)ctx.getBean("spacesBootstrap-mt"); - bootstrapSpacesTenantStore(spacesImporterBootstrap, tenantDomain); - - thumbnailRegistry.initThumbnailDefinitions(); - - // TODO janv - resolve this conflict later - /* Note: assume for now that all tenant deployers can lazily init - - // notify listeners that tenant has been created & hence enabled - for (TenantDeployer tenantDeployer : tenantDeployers) + if (tenantFileContentStore instanceof TenantDeployer) { - tenantDeployer.onEnableTenant(); - } - */ - - // bootstrap workflows - for (WorkflowDeployer workflowDeployer : workflowDeployers) - { - workflowDeployer.init(); + ((TenantDeployer)tenantFileContentStore).init(); } - // bootstrap modules (if any) - moduleService.startModules(); + // callback + RetryingTransactionCallback doImportCallback = new RetryingTransactionCallback() + { + public Object execute() throws Throwable + { + // create tenant-specific stores + ImporterBootstrap userImporterBootstrap = (ImporterBootstrap)ctx.getBean("userBootstrap-mt"); + bootstrapUserTenantStore(userImporterBootstrap, tenantDomain, tenantAdminRawPassword); + + ImporterBootstrap systemImporterBootstrap = (ImporterBootstrap)ctx.getBean("systemBootstrap-mt"); + bootstrapSystemTenantStore(systemImporterBootstrap, tenantDomain); + + // deprecated + ImporterBootstrap versionImporterBootstrap = (ImporterBootstrap)ctx.getBean("versionBootstrap-mt"); + bootstrapVersionTenantStore(versionImporterBootstrap, tenantDomain); + + ImporterBootstrap version2ImporterBootstrap = (ImporterBootstrap)ctx.getBean("version2Bootstrap-mt"); + bootstrapVersionTenantStore(version2ImporterBootstrap, tenantDomain); + + ImporterBootstrap spacesArchiveImporterBootstrap = (ImporterBootstrap)ctx.getBean("spacesArchiveBootstrap-mt"); + bootstrapSpacesArchiveTenantStore(spacesArchiveImporterBootstrap, tenantDomain); + + ImporterBootstrap spacesImporterBootstrap = (ImporterBootstrap)ctx.getBean("spacesBootstrap-mt"); + bootstrapSpacesTenantStore(spacesImporterBootstrap, tenantDomain); + + thumbnailRegistry.initThumbnailDefinitions(); + // TODO janv - resolve this conflict later + /* Note: assume for now that all tenant deployers can lazily init + + // notify listeners that tenant has been created & hence enabled + for (TenantDeployer tenantDeployer : tenantDeployers) + { + tenantDeployer.onEnableTenant(); + } + */ + + // bootstrap workflows + for (WorkflowDeployer workflowDeployer : workflowDeployers) + { + workflowDeployer.init(); + } + + // bootstrap modules (if any) + moduleService.startModules(); + + return null; + } + }; + + // if not default DB (ie. dbUrl != null) then run in new Spring managed txn (to ensure datasource is switched) + transactionService.getRetryingTransactionHelper().doInTransaction(doImportCallback, transactionService.isReadOnly(), (dbUrl != null)); return null; } - }; - - // if not default DB (ie. dbUrl != null) then run in new Spring managed txn (to ensure datasource is switched) - transactionService.getRetryingTransactionHelper().doInTransaction(doImportCallback, transactionService.isReadOnly(), (dbUrl != null)); + }, tenantDomain); } finally { @@ -620,35 +637,30 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo logger.warn("Tenant already enabled: " + tenantDomain); } - // Note: assume for now that all tenant deployers can lazily init - boolean notifyTenantDeployers = false; - enableTenant(tenantDomain, notifyTenantDeployers); - } - - protected void enableTenant(String tenantDomain, boolean notifyTenantDeployers) - { - // Check that all the passed values are not null - ParameterCheck.mandatory("tenantDomain", tenantDomain); - TenantUpdateEntity tenantUpdateEntity = tenantAdminDAO.getTenantForUpdate(tenantDomain); tenantUpdateEntity.setEnabled(true); tenantAdminDAO.updateTenant(tenantUpdateEntity); - if (notifyTenantDeployers) + notifyAfterEnableTenant(tenantDomain); + } + + protected void notifyAfterEnableTenant(String tenantDomain) + { + // Check that all the passed values are not null + ParameterCheck.mandatory("tenantDomain", tenantDomain); + + // notify listeners that tenant has been enabled + TenantUtil.runAsSystemTenant(new TenantRunAsWork() { - // notify listeners that tenant has been enabled - TenantUtil.runAsSystemTenant(new TenantRunAsWork() + public Object doWork() { - public Object doWork() + for (TenantDeployer tenantDeployer : tenantDeployers) { - for (TenantDeployer tenantDeployer : tenantDeployers) - { - tenantDeployer.onEnableTenant(); - } - return null; + tenantDeployer.onEnableTenant(); } - }, tenantDomain); - } + return null; + } + }, tenantDomain); if (logger.isInfoEnabled()) { @@ -670,33 +682,30 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo logger.warn("Tenant already disabled: " + tenantDomain); } - disableTenant(tenantDomain, true); - } - - protected void disableTenant(String tenantDomain, boolean notifyTenantDeployers) - { - tenantDomain = getTenantDomain(tenantDomain); - - if (notifyTenantDeployers) - { - // notify listeners that tenant has been disabled - TenantUtil.runAsSystemTenant(new TenantRunAsWork() - { - public Object doWork() - { - for (TenantDeployer tenantDeployer : tenantDeployers) - { - tenantDeployer.onDisableTenant(); - } - return null; - } - }, tenantDomain); - } + notifyBeforeDisableTenant(tenantDomain); // update tenant attributes / tenant cache - need to disable after notifying listeners (else they cannot disable) TenantUpdateEntity tenantUpdateEntity = tenantAdminDAO.getTenantForUpdate(tenantDomain); tenantUpdateEntity.setEnabled(false); tenantAdminDAO.updateTenant(tenantUpdateEntity); + } + + protected void notifyBeforeDisableTenant(String tenantDomain) + { + tenantDomain = getTenantDomain(tenantDomain); + + // notify listeners that tenant has been disabled + TenantUtil.runAsSystemTenant(new TenantRunAsWork() + { + public Object doWork() + { + for (TenantDeployer tenantDeployer : tenantDeployers) + { + tenantDeployer.onDisableTenant(); + } + return null; + } + }, tenantDomain); if (logger.isInfoEnabled()) { @@ -795,15 +804,13 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo } }, tenantDomain); - final String tenantAdminUser = getTenantAdminUser(tenantDomain); - // delete tenant-specific stores - nodeService.deleteStore(tenantService.getName(tenantAdminUser, new StoreRef(PROTOCOL_STORE_WORKSPACE, STORE_BASE_ID_SPACES))); - nodeService.deleteStore(tenantService.getName(tenantAdminUser, new StoreRef(PROTOCOL_STORE_ARCHIVE, STORE_BASE_ID_SPACES))); - nodeService.deleteStore(tenantService.getName(tenantAdminUser, new StoreRef(PROTOCOL_STORE_WORKSPACE, STORE_BASE_ID_VERSION1))); - nodeService.deleteStore(tenantService.getName(tenantAdminUser, new StoreRef(PROTOCOL_STORE_WORKSPACE, STORE_BASE_ID_VERSION2))); - nodeService.deleteStore(tenantService.getName(tenantAdminUser, new StoreRef(PROTOCOL_STORE_SYSTEM, STORE_BASE_ID_SYSTEM))); - nodeService.deleteStore(tenantService.getName(tenantAdminUser, new StoreRef(PROTOCOL_STORE_USER, STORE_BASE_ID_USER))); + nodeService.deleteStore(tenantService.getName(new StoreRef(PROTOCOL_STORE_WORKSPACE, STORE_BASE_ID_SPACES), tenantDomain, false)); + nodeService.deleteStore(tenantService.getName(new StoreRef(PROTOCOL_STORE_ARCHIVE, STORE_BASE_ID_SPACES), tenantDomain, false)); + nodeService.deleteStore(tenantService.getName(new StoreRef(PROTOCOL_STORE_WORKSPACE, STORE_BASE_ID_VERSION1), tenantDomain, false)); + nodeService.deleteStore(tenantService.getName(new StoreRef(PROTOCOL_STORE_WORKSPACE, STORE_BASE_ID_VERSION2), tenantDomain, false)); + nodeService.deleteStore(tenantService.getName(new StoreRef(PROTOCOL_STORE_SYSTEM, STORE_BASE_ID_SYSTEM), tenantDomain, false)); + nodeService.deleteStore(tenantService.getName(new StoreRef(PROTOCOL_STORE_USER, STORE_BASE_ID_USER), tenantDomain, false)); TenantUtil.runAsSystemTenant(new TenantRunAsWork() { @@ -1305,7 +1312,7 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo tenantAdminDAO.createTenant(tenantEntity); } - private void validateTenantName(String tenantDomain) + protected void validateTenantName(String tenantDomain) { ParameterCheck.mandatory("tenantDomain", tenantDomain); diff --git a/source/java/org/alfresco/repo/tenant/MultiTDemoTest.java b/source/java/org/alfresco/repo/tenant/MultiTDemoTest.java index 5ea9e63d52..246dc96243 100644 --- a/source/java/org/alfresco/repo/tenant/MultiTDemoTest.java +++ b/source/java/org/alfresco/repo/tenant/MultiTDemoTest.java @@ -1701,7 +1701,7 @@ public class MultiTDemoTest extends TestCase } - // TODO pending CLOUD-1350 fix + // TODO pending CLOUD-1351 fix public void xtest20_ALF_12732() { final String tenantDomain1 = TEST_RUN+".one.alf12732"; diff --git a/source/java/org/alfresco/repo/tenant/MultiTServiceImpl.java b/source/java/org/alfresco/repo/tenant/MultiTServiceImpl.java index 640540ae6c..118be354d0 100644 --- a/source/java/org/alfresco/repo/tenant/MultiTServiceImpl.java +++ b/source/java/org/alfresco/repo/tenant/MultiTServiceImpl.java @@ -117,7 +117,7 @@ public class MultiTServiceImpl implements TenantService assocRef.getTypeQName(), getName(assocRef.getTargetRef())); } - + /* (non-Javadoc) * @see org.alfresco.repo.tenant.TenantService#getName(java.lang.String, org.alfresco.service.cmr.repository.StoreRef) */ @@ -138,8 +138,24 @@ public class MultiTServiceImpl implements TenantService return storeRef; } + protected StoreRef getName(StoreRef storeRef, String tenantDomain, boolean checkTenantEnabled) + { + if (storeRef == null) { return null; } + if (tenantDomain != null) + { + storeRef = new StoreRef(storeRef.getProtocol(), getName(storeRef.getIdentifier(), tenantDomain, checkTenantEnabled)); + } + + return storeRef; + } + protected String getName(String name, String tenantDomain) { + return getName(name, tenantDomain, true); + } + + protected String getName(String name, String tenantDomain, boolean checkTenantEnabled) + { ParameterCheck.mandatory("tenantDomain", tenantDomain); if (name == null) @@ -147,7 +163,10 @@ public class MultiTServiceImpl implements TenantService return null; } - checkTenantEnabled(tenantDomain); + if (checkTenantEnabled) + { + checkTenantEnabled(tenantDomain); + } int idx1 = name.indexOf(SEPARATOR); if (idx1 != 0) @@ -644,6 +663,10 @@ public class MultiTServiceImpl implements TenantService return nameDomain; } + /** + * @deprecated + * @return + */ public static String getMultiTenantDomainName(String name) { // Check that all the passed values are not null diff --git a/source/java/org/alfresco/repo/tenant/RunAsTenantInterceptor.java b/source/java/org/alfresco/repo/tenant/RunAsTenantInterceptor.java index 612d520fb0..e7d6c9dd32 100644 --- a/source/java/org/alfresco/repo/tenant/RunAsTenantInterceptor.java +++ b/source/java/org/alfresco/repo/tenant/RunAsTenantInterceptor.java @@ -72,7 +72,8 @@ public class RunAsTenantInterceptor implements MethodInterceptor } else { - return TenantUtil.runAsPrimaryTenant(runAs, AuthenticationUtil.getFullyAuthenticatedUser()); + // run as tenant using current tenant context (if no tenant context then it is implied as the primary tenant, based on username) + return TenantUtil.runAsTenant(runAs, AuthenticationUtil.getUserTenant(AuthenticationUtil.getFullyAuthenticatedUser()).getSecond()); } } } diff --git a/source/java/org/alfresco/repo/tenant/TenantBasicDataSource.java b/source/java/org/alfresco/repo/tenant/TenantBasicDataSource.java index f62adb445a..8435450d64 100644 --- a/source/java/org/alfresco/repo/tenant/TenantBasicDataSource.java +++ b/source/java/org/alfresco/repo/tenant/TenantBasicDataSource.java @@ -36,14 +36,29 @@ public class TenantBasicDataSource extends BasicDataSource this.setUrl(tenantUrl); this.setMaxActive(tenantMaxActive == -1 ? bds.getMaxActive() : tenantMaxActive); - // defaults + // defaults/overrides - see also 'baseDefaultDataSource' (core-services-context.xml + repository.properties) + + this.setDriverClassName(bds.getDriverClassName()); this.setUsername(bds.getUsername()); this.setPassword(bds.getPassword()); - this.setDriverClassName(bds.getDriverClassName()); - this.setMaxIdle(bds.getMaxIdle()); + this.setInitialSize(bds.getInitialSize()); this.setMinIdle(bds.getMinIdle()); - - // TODO other default settings + this.setMaxIdle(bds.getMaxIdle()); + this.setDefaultAutoCommit(bds.getDefaultAutoCommit()); + this.setDefaultTransactionIsolation(bds.getDefaultTransactionIsolation()); + this.setMaxWait(bds.getMaxWait()); + this.setValidationQuery(bds.getValidationQuery()); + this.setTimeBetweenEvictionRunsMillis(bds.getTimeBetweenEvictionRunsMillis()); + this.setMinEvictableIdleTimeMillis(bds.getMinEvictableIdleTimeMillis()); + this.setNumTestsPerEvictionRun(bds.getNumTestsPerEvictionRun()); + this.setTestOnBorrow(bds.getTestOnBorrow()); + this.setTestOnReturn(bds.getTestOnReturn()); + this.setTestWhileIdle(bds.getTestWhileIdle()); + this.setRemoveAbandoned(bds.getRemoveAbandoned()); + this.setRemoveAbandonedTimeout(bds.getRemoveAbandonedTimeout()); + this.setPoolPreparedStatements(bds.isPoolPreparedStatements()); + this.setMaxOpenPreparedStatements(bds.getMaxOpenPreparedStatements()); + this.setLogAbandoned(bds.getLogAbandoned()); } } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/tenant/TenantRoutingFileContentStore.java b/source/java/org/alfresco/repo/tenant/TenantRoutingFileContentStore.java index 2756a4267a..9effa0b926 100644 --- a/source/java/org/alfresco/repo/tenant/TenantRoutingFileContentStore.java +++ b/source/java/org/alfresco/repo/tenant/TenantRoutingFileContentStore.java @@ -23,7 +23,6 @@ import java.io.Serializable; import java.util.HashMap; import java.util.Map; - import org.alfresco.repo.content.ContentLimitProvider; import org.alfresco.repo.content.ContentLimitProvider.NoLimitProvider; import org.alfresco.repo.content.ContentStore; diff --git a/source/java/org/alfresco/repo/thumbnail/ThumbnailRegistry.java b/source/java/org/alfresco/repo/thumbnail/ThumbnailRegistry.java index 9bd05bccad..a2017aa35f 100644 --- a/source/java/org/alfresco/repo/thumbnail/ThumbnailRegistry.java +++ b/source/java/org/alfresco/repo/thumbnail/ThumbnailRegistry.java @@ -57,17 +57,19 @@ public class ThumbnailRegistry implements ApplicationContextAware, ApplicationLi { /** Logger */ private static Log logger = LogFactory.getLog(ThumbnailRegistry.class); - + /** Content service */ private ContentService contentService; /** Transaction service */ - private TransactionService transactionService; + protected TransactionService transactionService; /** Rendition service */ - private RenditionService renditionService; + protected RenditionService renditionService; - private TenantAdminService tenantAdminService; + protected TenantAdminService tenantAdminService; + + private boolean redeployStaticDefsOnStartup; /** Map of thumbnail definition */ private Map thumbnailDefinitions = new HashMap(); @@ -124,7 +126,12 @@ public class ThumbnailRegistry implements ApplicationContextAware, ApplicationLi { this.tenantAdminService = tenantAdminService; } - + + public void setRedeployStaticDefsOnStartup(boolean redeployStaticDefsOnStartup) + { + this.redeployStaticDefsOnStartup = redeployStaticDefsOnStartup; + } + /** * This method is used to inject the thumbnail definitions. * @param thumbnailDefinitions @@ -348,12 +355,23 @@ public class ThumbnailRegistry implements ApplicationContextAware, ApplicationLi lifecycle.onApplicationEvent(event); } + protected boolean redeploy() + { + return AuthenticationUtil.runAs(new RunAsWork() + { + public Boolean doWork() throws Exception + { + return ((getThumbnailDefinitions().size() > 0) && (redeployStaticDefsOnStartup || renditionService.loadRenditionDefinitions().size() == 0)); + } + }, AuthenticationUtil.getSystemUserName()); + } + /** * This class hooks in to the spring application lifecycle and ensures that any * ThumbnailDefinitions injected by spring are converted to RenditionDefinitions * and saved. */ - private class RegistryLifecycle extends AbstractLifecycleBean + protected class RegistryLifecycle extends AbstractLifecycleBean { /* (non-Javadoc) * @see org.alfresco.util.AbstractLifecycleBean#onBootstrap(org.springframework.context.ApplicationEvent) @@ -361,49 +379,52 @@ public class ThumbnailRegistry implements ApplicationContextAware, ApplicationLi @Override protected void onBootstrap(ApplicationEvent event) { - long start = System.currentTimeMillis(); - - // If the database is in read-only mode, then do not persist the thumbnail definitions. - if (transactionService.isReadOnly()) + if (redeploy()) { - if (logger.isDebugEnabled()) + long start = System.currentTimeMillis(); + + // If the database is in read-only mode, then do not persist the thumbnail definitions. + if (transactionService.isReadOnly()) { - logger.debug("TransactionService is in read-only mode. Therefore no thumbnail definitions have been initialised."); - } - return; - } - - AuthenticationUtil.runAs(new RunAsWork() - { - public Object doWork() throws Exception - { - initThumbnailDefinitions(); - return null; - } - }, AuthenticationUtil.getSystemUserName()); - - if (tenantAdminService.isEnabled()) - { - List tenants = tenantAdminService.getAllTenants(); - for (Tenant tenant : tenants) - { - AuthenticationUtil.runAs(new RunAsWork() + if (logger.isDebugEnabled()) { - public Object doWork() throws Exception - { - initThumbnailDefinitions(); - return null; - } - }, tenantAdminService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenant.getTenantDomain())); + logger.debug("TransactionService is in read-only mode. Therefore no thumbnail definitions have been initialised."); + } + return; + } + + AuthenticationUtil.runAs(new RunAsWork() + { + public Object doWork() throws Exception + { + initThumbnailDefinitions(); + return null; + } + }, AuthenticationUtil.getSystemUserName()); + + if (tenantAdminService.isEnabled()) + { + List tenants = tenantAdminService.getAllTenants(); + for (Tenant tenant : tenants) + { + AuthenticationUtil.runAs(new RunAsWork() + { + public Object doWork() throws Exception + { + initThumbnailDefinitions(); + return null; + } + }, tenantAdminService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenant.getTenantDomain())); + } + } + + if (logger.isInfoEnabled()) + { + logger.info("Init'ed thumbnail defs in "+(System.currentTimeMillis()-start)+" ms"); } - } - - if (logger.isInfoEnabled()) - { - logger.info("Init'ed thumbnail defs in "+(System.currentTimeMillis()-start)+" ms"); } } - + /* (non-Javadoc) * @see org.alfresco.util.AbstractLifecycleBean#onShutdown(org.springframework.context.ApplicationEvent) */ diff --git a/source/java/org/alfresco/repo/version/VersionServiceImpl.java b/source/java/org/alfresco/repo/version/VersionServiceImpl.java index 76cc2738f4..43d5e8f71b 100644 --- a/source/java/org/alfresco/repo/version/VersionServiceImpl.java +++ b/source/java/org/alfresco/repo/version/VersionServiceImpl.java @@ -956,7 +956,7 @@ public class VersionServiceImpl extends AbstractVersionServiceImpl implements Ve } // Do we need to create the initial version history entry? By convention this is always a major version. - if(getVersionHistory(nodeRef) == null) + if(getVersionHistoryNodeRef(nodeRef) == null) { createVersion(nodeRef, Collections.singletonMap(VersionModel.PROP_VERSION_TYPE, VersionType.MAJOR)); } diff --git a/source/java/org/alfresco/repo/workflow/WorkflowNotificationUtils.java b/source/java/org/alfresco/repo/workflow/WorkflowNotificationUtils.java index e5c9d782c1..deb020e2d7 100644 --- a/source/java/org/alfresco/repo/workflow/WorkflowNotificationUtils.java +++ b/source/java/org/alfresco/repo/workflow/WorkflowNotificationUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2011 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -25,6 +25,7 @@ import java.util.List; import java.util.Map; import org.alfresco.repo.notification.EMailNotificationProvider; +import org.alfresco.repo.tenant.TenantUtil; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.notification.NotificationContext; import org.alfresco.service.cmr.repository.ChildAssociationRef; @@ -58,9 +59,10 @@ public abstract class WorkflowNotificationUtils public static final String ARG_WF_PRIORITY = "workflowPriority"; public static final String ARG_WF_POOLED = "workflowPooled"; public static final String ARG_WF_DOCUMENTS = "workflowDocuments"; + public static final String ARG_WF_TENANT = "workflowTenant"; /** Standard workflow assigned template */ - public static final NodeRef WF_ASSIGNED_TEMPLATE = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, "wf-email-html-ftl"); + public static String WF_ASSIGNED_TEMPLATE = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, "wf-email-html-ftl").toString(); /** * @@ -99,7 +101,7 @@ public abstract class WorkflowNotificationUtils notificationContext.setSubject(subject); // Set the email template - notificationContext.setBodyTemplate(services.getFileFolderService().getLocalizedSibling(WF_ASSIGNED_TEMPLATE)); + notificationContext.setBodyTemplate(WF_ASSIGNED_TEMPLATE); // Build the template args MaptemplateArgs = new HashMap(7); @@ -135,6 +137,13 @@ public abstract class WorkflowNotificationUtils } } + // Add tenant, if in context of tenant + String tenant = TenantUtil.getCurrentDomain(); + if (tenant != null) + { + templateArgs.put(ARG_WF_TENANT, tenant); + } + // Set the template args notificationContext.setTemplateArgs(templateArgs); diff --git a/source/java/org/alfresco/service/cmr/notification/NotificationContext.java b/source/java/org/alfresco/service/cmr/notification/NotificationContext.java index 2385dd1ba5..a944b491b7 100644 --- a/source/java/org/alfresco/service/cmr/notification/NotificationContext.java +++ b/source/java/org/alfresco/service/cmr/notification/NotificationContext.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2011 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -46,7 +46,7 @@ public class NotificationContext private String body; /** Template node used to generate the body of the notification */ - private NodeRef bodyTemplate; + private String bodyTemplate; /** Template arguments (appear as map under 'arg' property in template model) */ private Map templateArgs; @@ -135,12 +135,12 @@ public class NotificationContext } /** - * The body template is a node reference to a template that can be executed with the given + * The body template is a node re or classpath ref to a template that can be executed with the given * template arguments to produce the body of the notification. * * @param bodyTemplate body template */ - public void setBodyTemplate(NodeRef bodyTemplate) + public void setBodyTemplate(String bodyTemplate) { this.bodyTemplate = bodyTemplate; } @@ -148,10 +148,10 @@ public class NotificationContext /** * @return {@link NodeRef} body template */ - public NodeRef getBodyTemplate() + public String getBodyTemplate() { return bodyTemplate; - } + } /** * The template arguments are used as context for the body template when it is executed. Any values placed in this map will diff --git a/source/java/org/alfresco/service/cmr/security/PersonService.java b/source/java/org/alfresco/service/cmr/security/PersonService.java index 4715b3ec0d..613b26d4b2 100644 --- a/source/java/org/alfresco/service/cmr/security/PersonService.java +++ b/source/java/org/alfresco/service/cmr/security/PersonService.java @@ -102,7 +102,17 @@ public interface PersonService */ @Auditable(parameters = {"userName", "autoCreate"}) public NodeRef getPerson(final String userName, final boolean autoCreateHomeFolderAndMissingPersonIfAllowed); - + + /** + * Retrieve the person info for an existing {@code person NodeRef} + * + * @param person NodeRef + * @return PersonInfo (username, firstname, lastname) + * @throws NoSuchPersonException if the person doesn't exist + */ + @Auditable(parameters = {"personRef"}) + public PersonInfo getPerson(NodeRef personRef) throws NoSuchPersonException; + /** * Check if a person exists. *