From 182c29aac27829f3580ba72bbd96ab82b04cf0e3 Mon Sep 17 00:00:00 2001 From: Dave Ward Date: Fri, 23 Nov 2012 16:46:57 +0000 Subject: [PATCH] Merged V4.1-BUG-FIX to HEAD 43628: Fix for ALF-16299 - On full user profile, in the 'Sites' tab, the site descriptions are not aligned when a long site description is provided. 43639: ALF-16701: use newly released Spring Surf 1.2.0-M1 rather than a SNAPSHOT 43644: ALF-16527: webscript person.lib.ftl does not include all attributes for users out of the box. - added missing organizationId attribute. 43649: ALF-16756: WebDAV: An error occurs on drag&drop content from local machine to alfresco when inbound move rule configured. 43651: ALF-16756: Fixed typos - I took this code in good faith! 43659: ALF-16006: MT: Document Library is absent after upgrade from 3.4.x to 4.1.x (eg. 3.4.10 -> 4.1.1) - More patch dependencies required 43666: ALF-16833 / MNT-187: Fixed regression in inbound rule firing caused by the fix to ALF-14744 - Inbound rules were not firing for newly created content nodes that had null content properties (e.g. dataListItems) - Now the ALF-14744 fix has been revised so that inbound rules are only not fired for new nodes with zero-length content, as possibly created by the OSX / Windows 7 WebDAV clients during an upload, and only if policy.content.update.ignoreEmpty=true (the default) - OnContentUpdateRuleTrigger removed. Now all content property updates are handled by OnPropertyUpdateRuleTrigger so that we can have this subtle treatment of 'empty' content properties. - Reverted ALF-14744 changes to RuleServiceCoverageTest and added new tests for 'empty' content and ASPECT_NO_CONTENT - Updated RuleTriggerTest in line with changes 43675: Merged V4.1 to V4.1-BUG-FIX 43674: Merged PATCHES/V4.1.1 to V4.1 43673: Merged V3.4-BUG-FIX to PATCHES/V4.1.1 43672: ALF-16834, ALF-16833, MNT-187: Fix unit test failures and 'fine tune' logic to handle multiple updates within a single transaction creating a node 43676: ALF-12324: Can't delete site with WQS content - Don't try to add nodes to the publish queue on site deletion 43689: Fix for ALF-14207 - cm:link not correctly handled in Share's doclist when users don't have permission 43690: Fix for ALF-16538 - Wrong label on change group permission message 43696: ALF-16348 (Inconsistent error message when latest manager leaves a site) 43719: ALF-16031: "CMISChangeLog does not log Created events" 43720: ALF-14137: "When calling CMIS getAllVersions method using the OpenCMIS Browser the PreparedStatement is executed multiple times" 43722: ALF-16352: Re-instated manage permissions action to document list view 43745: Fix for ALF-15351. ContentModelFormProcessor had a TODO to handle associations defined on aspects other than those aspects already on the node for which a form is being submitted. I've added code that, when an association is added to a node, will check if the association is defined on any aspect in the system and if it is, it will let the association be created. 43757: ALF-16171: If a password for admin user is specified with a space in the end during the installation Alfresco startup fails - Added validation that will not allow the specified password to contain leading to trailing white space 43760: Fixed ALF-16317 "Labels not displaying full text when creating a rule on a folder with IE8" part 2 43769: Fix for ALF-13461. Merged PATCHES/V3.4.8 to BRANCHES/DEV/V4.1-BUG-FIX: 43344: MNT-162: Merged DEV to PATCHES/V3.4.8 43254: MNT-162: CLONE - Hidden folder "Comments" is available to searching - Removed last '/' from COMMENT_QNAMEPATH constant. 43770: ALF-15616: Merged V3.4-BUG-FIX (3.4.12) to V4.1-BUG-FIX (4.1.3) 43768: Merged Dev to V3.4-BUG-FIX (3.4.12) 43743: ALF-11956: WCM accessibility The problem with absence of initial focus on the first Web form element has been resolved in 'alfresco.xforms.XForm._loadHandler()'. This method creates all controls, defined in the form. 'alfresco.xforms.FocusResolver._findControl()' has been renamed to a public-like method ('alfresco.xforms.FocusResolver.findControl()'). Also it has been modified to introduce a possibility of searching elements, using root XForms widgets container. Some other minor fixes. 43772: Fixed ALF-16497 "Selected Items drop down box is expanded to the right in IE9" 43787: Merged in Philippe's fix for ALF-16313:unmakeTranslation() called on pivot language gives exception 43794: ALF-16155: "Notification digest accumulates and is repeatedly sent if 1 or more notification email fails" 43805: ALF-16212: Fixes double encoding issue. 43835: Fixed ALF-13595: Patches third party library date.js to prevent infinite loop. 43836: ALF-16480: Merged PATCHES/V4.1.1 to V4.1-BUG-FIX 43252: MNT-166: Document lock not removed after the lock expiry date has been reached - Changed evaluator "evaluator.doclib.metadata.isLocked" to use "evaluator.doclib.indicator.nodeLocked" and "evaluator.doclib.indicator.lockOwner" evaluator. 43253: MNT-165: "Cancel Editing" does not completely remove lock from document - Added documentLibrary cancel editing action for locked documents. 43300: MNT-171: Merged V4.1-BUG-FIX to PATCHES/V4.1.1 (modified) 43096: Fix for ALF-16283 - When document is checked out, 'Edit Online' and 'Upload New Version' options should not be visible on the original document. 43311: MNT-165: "Cancel Editing" does not completely remove lock from document - Fix for page refresh problem when cancel editing on details page 43421: MNT-186: 4.1.1.7 HF: Webscipt error on doclib page, containing locked by other users files - Change evaluator.doclib.metadata.isLocked to break circular dependency 43755: MNT-202: Upload New Version not available for a document that has been edited offline - Upload New Version is now available if editable by user (respecting locks, type of checkout, etc). 43844: ALF-16696: Merged DEV to V4.1-BUG-FIX 43734: Share nodebrowser is unable to access node of a document with MS residual properties - Use localname if no prefix is registered for a namespace uri 43864: Fixed ALF-16320 "Properties side panel collapses after editing properties in document preview window with IE8" 43866: Fixed ALF-16320 "Properties side panel collapses after editing properties in document preview window with IE8" part 2 - Checking using YAHOO.util.Event.getTarget instead 43867: Fixed ALF-16276 "'"Web View' dahslet displays 2 scrollbars in IE8." 43872: Merged V4.1 to V4.1-BUG-FIX 43622: ALF-16757: Sharepoint doesn't work correct with SSO - Fix by Pavel 43633: Latest translations from Gloria (r43623) 43636: Merged PATCHES/V4.1.1 to V4.1 43301: ALF-16811 / MNT-173: SOLR tracking spending too long evaluating paths - Too much time was being spent fetching the individual nodes in each path and there could be potentially thousands - Now we traverse all the ancestor parent associations in the cache before switching shared cache reads off, then bulk load them in one shot! 43303: ALF-16812 / MNT-174: "dictionaryDAO.init() called, yet no namespace registry for domain" after node rejoins cluster - namespaceRegistryCache is secondary to the clustered dictionaryRegistryCache, so can be a non-clustered cache. Synchronization and thread locals already in use so still thread safe. - dictionaryDAO.init() now has sole responsibility of establishing the NamespaceRegistry threadlocal within its locks, so no more risk of cyclic dependencies, race conditions, partial initialization, or registries dropping out of the cache - To avoid being confused by the 'consistent read' behaviour of a transactional cache, DictionaryDAOImpl now reads / writes directly to a shared cache. Again locks and thread locals still used so still thread safe 43334: ALF-16812 / MNT-174: Fixed failing unit tests - Because DictionaryDAOImpl now reads directly from the shared cache we need to reset it on initialization to avoid problems in multi-context unit tests 43337: ALF-16811 / MNT-173: Fix test failure. Cope with IDs of deleted nodes in getCachedAncestors() 43356: ALF-16811 / MNT-173: If we are disabling shared cache reads for the transaction, convert all existing reads and updates to avoid 'consistent read' behaviour giving us a potentially out of date node already accessed - Existing read buckets are simply thrown away - Updates are converted to removes to avoid any assumptions about existing shared cache content - New entries are left alone as they haven't come from the shared cache anyway 43410: ALF-16813 / MNT-185: Web Scripts are being endlessly re-registering in clustered environment - Due to 'consistent read' behaviour of transactional cache - As accesses are regulated by RW locks we can read straight through to the shared cache instead 43565: ALF-16814 / MNT-190: Bmlab Solr Node 2 threw unhandled NullPointerException (and possibly made solr unresponsive) - Added missing return statement 43646: ALF-15755: Rationalization of WebDAVMethod.checkNode() 43681: Merged HEAD to V4.1 43656: Fix for ALF-16683 CMIS: cannot navigate to original document was created through CMIS with CHECKED OUT version state. 43698: Latest Russian translations from Gloria 43838: ALF-16875: Merged V4.1-BUG-FIX to V4.1 43836: ALF-16480: Merged PATCHES/V4.1.1 to V4.1-BUG-FIX 43252: MNT-166: Document lock not removed after the lock expiry date has been reached - Changed evaluator "evaluator.doclib.metadata.isLocked" to use "evaluator.doclib.indicator.nodeLocked" and "evaluator.doclib.indicator.lockOwner" evaluator. 43253: MNT-165: "Cancel Editing" does not completely remove lock from document - Added documentLibrary cancel editing action for locked documents. 43300: MNT-171: Merged V4.1-BUG-FIX to PATCHES/V4.1.1 (modified) 43096: Fix for ALF-16283 - When document is checked out, 'Edit Online' and 'Upload New Version' options should not be visible on the original document. 43311: MNT-165: "Cancel Editing" does not completely remove lock from document - Fix for page refresh problem when cancel editing on details page 43421: MNT-186: 4.1.1.7 HF: Webscipt error on doclib page, containing locked by other users files - Change evaluator.doclib.metadata.isLocked to break circular dependency 43755: ALF-16890 / MNT-202: Upload New Version not available for a document that has been edited offline - Upload New Version is now available if editable by user (respecting locks, type of checkout, etc). 43868: Reverse merging r43838 - Merge in wrong direction introducing duplicate fragment into share-documentlibrary-config.xml 43871: ALF-16890: Merged PATCHES/V4.1.1 to V4.1 43755: MNT-202: Upload New Version not available for a document that has been edited offline - Upload New Version is now available if editable by user (respecting locks, type of checkout, etc). 43873: Merged V4.1 to V4.1-BUG-FIX (RECORD ONLY) 43602: ALF-16254: Merged V4.1-BUG-FIX to V4.1 43598: Merged HEAD to BRANCHES/DEV/V4.1-BUG-FIX 41906: ALF-11378: REST API has been modified to return extra information about a user whether s/he belongs to a group or not. 43612: ALF-16598: Merged V4.1-BUG-FIX to V4.1 43252: MNT-166: Document lock not removed after the lock expiry date has been reached - Changed evaluator "evaluator.doclib.metadata.isLocked" to use "evaluator.doclib.indicator.nodeLocked" and "evaluator.doclib.indicator.lockOwner" evaluator. 43254: MNT-165: "Cancel Editing" does not completely remove lock from document - Added documentLibrary cancel editing action for locked documents. 43300: MNT-171: Merged V4.1-BUG-FIX to PATCHES/V4.1.1 (modified) 43096: Fix for ALF-16283 - When document is checked out, 'Edit Online' and 'Upload New Version' options should not be visible on the original document. 43311: MNT-165: "Cancel Editing" does not completely remove lock from document - Fix for page refresh problem when cancel editing on details page 43421: MNT-186: 4.1.1.7 HF: Webscipt error on doclib page, containing locked by other users files - Change evaluator.doclib.metadata.isLocked to break circular dependency 43615: ALF-16794: Merged V4.1-BUG-FIX to V4.1 43478: MNT-181: Now WebDAV will ALWAYS preserve the original metadata and versions of ANY node that is temporarily 'moved out' in ANY kind of 'shuffle' operation - To make the source node temporarily invisible to WebDAV the client specific HIDDEN aspect features are used - WebDAVHelper.isRenameShuffle() method introduced, to parallel ALF-3856 CIFS fix and using similar system.webdav.renameShufflePattern global property to detect the start of a shuffle - WebDAVHelper converted to use proper dependency injection - CopyMethod has become a simple subclass of MoveMethod as all the hidden aspect munging is done by it - DeleteMethod now preserves hidden nodes - PropFindMethod now ignores hidden nodes - Listing methods will hide hidden nodes from WebDAV 43483: MNT-181: Corrected typo 43523: MNT-181: Corrections - WebDAVLockService.unlock() made 'harmless' to call on already-unlocked nodes - Delete method hides rather than deletes versioned nodes and working copes in case it is called by OSX Finder during a 'replace' operation 43524: MNT-181: Correction - PutMethod now 'unhides' hidden nodes and behaves as though it created them 43570: MNT-181: More corrections researched by Valery - Don't treat all moves to temporary locations as copies - just those from non-temporary locations. Avoids initial upload leaving lots of hidden files around. - Only copy the content, not the whole node including aspects to avoid versioning temporary files! - Don't version on changes to sys:clientVisibilityMask - avoids 'double versioning' - Recognize Mac .TemporaryItems folder and ._ files as temporary 43586: MNT-181: Final correction researched by Valery - Corrected system.webdav.renameShufflePattern so that it matches .TemporaryItems folder and ._ files as a full match 43616: ALF-15755: Merged V4.1-BUG-FIX to V4.1 43591: ALF-16772: If the WebDAV path of a document exceeds 255 characters, documents opened in MSOffice cannot be saved back - Interpret null nodeLockToken as not locked. 43629: Merged V4.1-BUG-FIX to V4.1 (4.1.2) 43498: Fix for ALF-16648 - Alfresco Enterprise artifacts in artifacts.alfresco.com do not provide POM files / dependencies declarations: Merged HEAD to V4.1-BUG-FIX (4.1.2) 43380: -- added site content for alfresco-platform-distribution POM 43379: -- added site documentation for alfresco-platform-distribution POM 43378: -- added site documentation for alfresco-platform-distribution POM -- deployed site for 4.2.b Community at https://artifacts.alfresco.com/nexus/content/repositories/alfresco-docs/alfresco-platform-distribution/latest/index.html -- created repository for Enterprise docs and added url in the appropriate edition properties 43273: Use property to define POI version 42966: ALF-14353 - Added platform distribution POM to standard maven-deploy procedure 42965: ALF-14353 - added alfresco-platform-distribution to provide a Maven release descriptor (dependencyManagement) per each Community / Enterprise release -- moved maven-ant-tasks not to be in the runtime lib -- added platform distribution pom in the SDK folder -- updated maven.xml to deploy filter and deploy the appropriate platform-distribution POM per each releae -- in maven.xml moved configure-release and configure-snapshot goals to maven-env-prerequisites -- updated sdk readme to explain the presence of alfresco-platform-distribution POM 42912: -- updated README header on the POM specifying it's NOT usable to build Alfresco -- make a clear reference to the POMs that get deployed by pom-experimental.xml being usable for development 42842: ALF-14353: Fix artifactId alfresco-jlan -> alfresco-jlan-embed 41883: ALF-14353 - fixed multiple Maven build issues. Now mvn clean install -f pom-experimental.xml works fine. Also deployed Spring Surf 1.2.0-SNAPSHOT so proper Surf version is retrieved 41882: added pre-requisites to build POMs successfully with mvn clean install -f pom-experimental.xml 43634: Merged V4.1-BUG-FIX to V4.1 43386: ALF-13091: Prevent bean post processor propagation to child application contexts. Remove Jsr250BeanPostPorcessor from the CXF configuration, to prevent strange interaction with component scanning. 43641: Merged V4.1-BUG-FIX to V4.1 (4.1.2) 43639: ALF-16701: use newly released Spring Surf 1.2.0-M1 in POM files rather than a SNAPSHOT 43645: Merged V4.1-BUG-FIX to V4.1 43644: ALF-16527: webscript person.lib.ftl does not include all attributes for users out of the box. - added missing organizationId attribute. 43660: Merged V4.1-BUG-FIX to V4.1 43659: ALF-16006: MT: Document Library is absent after upgrade from 3.4.x to 4.1.x (eg. 3.4.10 -> 4.1.1) - More patch dependencies required 43669: Merged V4.1-BUG-FIX to V4.1 43666: ALF-16833 / MNT-187: Fixed regression in inbound rule firing caused by the fix to ALF-14744 - Inbound rules were not firing for newly created content nodes that had null content properties (e.g. dataListItems) - Now the ALF-14744 fix has been revised so that inbound rules are only not fired for new nodes with zero-length content, as possibly created by the OSX / Windows 7 WebDAV clients during an upload, and only if policy.content.update.ignoreEmpty=true (the default) - OnContentUpdateRuleTrigger removed. Now all content property updates are handled by OnPropertyUpdateRuleTrigger so that we can have this subtle treatment of 'empty' content properties. - Reverted ALF-14744 changes to RuleServiceCoverageTest and added new tests for 'empty' content and ASPECT_NO_CONTENT - Updated RuleTriggerTest in line with changes 43697: Merged V4.1-BUG-FIX to V4.1 43689: Fix for ALF-14207 - cm:link not correctly handled in Share's doclist when users don't have permission 43761: Merged V4.1-BUG-FIX to V4.1 43760: Fixed ALF-16317 "Labels not displaying full text when creating a rule on a folder with IE8" part 2 43796: Merged V4.1-BUG-FIX to V4.1 43795: Fix for ALF-16254 - "Leave Site" behaviour for group based site membership: Blah, Blah, Blah! 43883: Fix for ALF-12711. Separated preparation of email message from sending of email message as described in analysis in JIRA. Did not merge the fix provided as MailActionExecuter.java.diff but reimplemented a fix based on that. 43888: ALF-16781: Merged V3.4-BUG-FIX (3.4.12) to V4.1-BUG-FIX (4.1.3) 43887: ALF-16898 CLONE - Transformation Server history shows incorrect transformation "To" type for images - ImageTransformActionExecuter ("Transform and Copy Image") re-factored to use ContentService rather than hard coded to ImageMagick 43900: Merged V4.1 to V4.1-BUG-FIX 43898: Merged PATCHES/V4.1.1 to V4.1 43708: ALF-16903 / MNT-203: ACL changes not propagated between two non clustered Alfresco instances hitting same Solr and Database - Although SOLRTrackingComponentImpl was not trusting the cache when tracking node transactions, it was still trusting the cache for ACL change sets - This mean that when the SOLR node is using an Alfresco out of the cluster (which we are supposed to support) it could see stale ACLs - Fixed this by using the same strategy we use in AbstractNodeDAO - Now the main acl CRUD cache is set to ignore the shared cache during SOLR tracking - All secondary ACL caches are keyed by ACL ID AND version, so it prevents the possibility of retrieving a stale cached ACL for an old version 43713: ALF-16903 / MNT-203: ACL changes not propagated between two non clustered Alfresco instances hitting same Solr and Database - Fix test failures - deleteAccessControlEntries must 'touch' all the ACLs it affects to keep caches valid - createAccessControlList should return the newly-versioned ACL 43736: ALF-16904 / MNT-204: Index showing no progress on Solr server - SAP's tracking thread got stuck indefinitely waiting on a content response after sending a request, probably due to some misbehaving proxy or balancer - Now we make it possible to recover from this - A new alfresco.socketTimeout parameter is now supported in solrcore.properties - It specifies the number of milliseconds SOLR will wait before giving up waiting for data on an HTTP connection - The default is still zero which means wait indefinitely 43759: ALF-16904 / MNT-204: Fixed compilation error 43899: Merged PATCHES/V4.1.1 to V4.1 (RECORD ONLY) 43667: Merged V4.1-BUG-FIX to PATCHES/V4.1.1 43666: ALF-16833 / MNT-187: Fixed regression in inbound rule firing caused by the fix to ALF-14744 - Inbound rules were not firing for newly created content nodes that had null content properties (e.g. dataListItems) - Now the ALF-14744 fix has been revised so that inbound rules are only not fired for new nodes with zero-length content, as possibly created by the OSX / Windows 7 WebDAV clients during an upload, and only if policy.content.update.ignoreEmpty=true (the default) - OnContentUpdateRuleTrigger removed. Now all content property updates are handled by OnPropertyUpdateRuleTrigger so that we can have this subtle treatment of 'empty' content properties. - Reverted ALF-14744 changes to RuleServiceCoverageTest and added new tests for 'empty' content and ASPECT_NO_CONTENT - Updated RuleTriggerTest in line with changes 43901: Merged V3.4-BUG-FIX to V4.1-BUG-FIX 43571: Merged DEV to V3.4-BUG-FIX 43569: ALF-16222: It's impossible to delete a file/message via IMAP using Microsoft Entourage 2008 in MacOSX 10.8 fixed: UID failed.Existing file or folder error on attempt to delete file if deleted items already contains file with the same name. modified: AttachmentExtractor modified to avoid code dublication. 43599: Fix for ALF-16505 - Discussion topics are sorted in ascending order (oldest first and newest last) when selecting 'All topics' 43610: Merged Dev to V3.4-BUGFIX (3.4.12) 43416: ALF-16470: SPP:Meeting recurent events are displayed incorrect in share calendar in all tabs (Day, Week, Month) Incorrect lucene query to search for events (it was search for events ONLY after fromDate, that gets from browser query. And recursive events, that have startDate before browser's query date, wasn't included into summary query for search for events. Start date is incorrect and is sets two times. Interval for month of end date is to small. Recursive event, that was started in previous month, and ends in current month, isn't included into result of search. For events, that ends on next date after start date, displays (view by month) only start date. 43625: ALF-11817: Cope with incomplete lock token headers from Microsoft-WebDAV-MiniRedir without the enclosing <> by just consuming the whole string 43670: ALF-11817: Prevent auto-hidden dot underscore files from reappearing on a put by checking for a shuffle path before 'unhiding' 43746: Merged DEV to V3.4-BUG-FIX (with corrections) 43692: ALF-16808 Webdav: Two versions of document have been added after the document has been rewritten once more via drag and drop action. 1. Checked whether the current content property is empty. 2. Disabled the versionable aspect. 3. Added the new content to the node. 43763: Fix for ALF-14828 - Incorrect behavior on delete action (WCMQS site) 43771: Fix for ALF-12752 - Custom form appearance parameters not accounted for causing ovverlapped textareas xforms changing their height 43773: Merged DEV to V3.4-BUG-FIX (3.4.12) 42010: ALF-14040: Event start/end time displays incorrect on MS Outlook Calendar and Calendar of created Meeting workspace 1) Send a date for "Site Calendar" dashlet in ISO-8601 format (like in v4.1.1), then after transformation to client's time zone it is displayed correct 2) For "My Calendar" dashlet we need to take into account that if the event is "all day event", then date should be used without time zone transformation 43804: ALF-12326 HomeFolderProviderSynchronizer fails to move any user space which has a rule configured on it 43837: Fixes ALF-12145: Date.js patches merged to 3.4 Merged BRANCHES/DEV/V4.0-BUG-FIX to BRANCHES/DEV/V3.4-BUG-FIX: 36202: ALF-13483: Japanese: Incorrect date handle in a date Input filed Merged BRANCHES/DEV/V4.1-BUG-FIX to BRANCHES/DEV/V3.4-BUG-FIX: 43835: Fixed ALF-13595: Patches third party library date.js to prevent infinite loop. 43839: ALF-16869: Merged PATCHES/V3.4.8 to V3.4-BUG-FIX 43344: MNT-162: Merged DEV to PATCHES/V3.4.8 43254: MNT-162: CLONE - Hidden folder "Comments" is available to searching - Removed last '/' from COMMENT_QNAMEPATH constant. 43902: Merged V3.4-BUG-FIX to V4.1-BUG-FIX (RECORD ONLY) 43177: Merged DEV to V3.4-BUG-FIX 43087: ALF-16474: Records Management groups were not deleted after removing RM site - Delete Records Management groups on ASPECT_RECORDS_MANAGEMENT_ROOT delete - Backport of RM-190 from RM 2.0 43228: ALF-16266: Merged HEAD to V3.4-BUG-FIX 32846: Fixes: ALF-10519: Issues with translation of roles. 31413: Fixes: ALF-10519 - Internationalises the role names for the repo browser's manage permissions page & makes these available to other pages through common.properties 43229: ALF-16266: Fix conflict data left in the properties file. 43624: ALF-11817: Merged PATCHES/V4.0.2 to V3.4-BUG-FIX 43587: Merged BRANCHES/V4.1-BUG-FIX to PATCHES/V4.0.2 42363: ALF-16213: renaming versioned file results in file being deleted. 43478: MNT-181: Now WebDAV will ALWAYS preserve the original metadata and versions of ANY node that is temporarily 'moved out' in ANY kind of 'shuffle' operation - To make the source node temporarily invisible to WebDAV the client specific HIDDEN aspect features are used - WebDAVHelper.isRenameShuffle() method introduced, to parallel ALF-3856 CIFS fix and using similar system.webdav.renameShufflePattern global property to detect the start of a shuffle - WebDAVHelper converted to use proper dependency injection - CopyMethod has become a simple subclass of MoveMethod as all the hidden aspect munging is done by it - DeleteMethod now preserves hidden nodes - PropFindMethod now ignores hidden nodes - Listing methods will hide hidden nodes from WebDAV 43483: MNT-181: Corrected typo 43523: MNT-181: Corrections - WebDAVLockService.unlock() made 'harmless' to call on already-unlocked nodes - Delete method hides rather than deletes versioned nodes and working copies in case it is called by OSX Finder during a 'replace' operation 43524: MNT-181: Correction - PutMethod now 'unhides' hidden nodes and behaves as though it created them 43570: MNT-181: More corrections researched by Valery - Don't treat all moves to temporary locations as copies - just those from non-temporary locations. Avoids initial upload leaving lots of hidden files around. - Only copy the content, not the whole node including aspects to avoid versioning temporary files! - Don't version on changes to sys:clientVisibilityMask - avoids 'double versioning' - Recognize Mac .TemporaryItems folder and ._ files as temporary 43586: MNT-181: Final correction researched by Valery - Corrected system.webdav.renameShufflePattern so that it matches .TemporaryItems folder and ._ files as a full match 43671: ALF-16834: Merged V4.1-BUG-FIX to V3.4-BUG-FIX 43666: ALF-16833 / MNT-187: Fixed regression in inbound rule firing caused by the fix to ALF-14744 - Inbound rules were not firing for newly created content nodes that had null content properties (e.g. dataListItems) - Now the ALF-14744 fix has been revised so that inbound rules are only not fired for new nodes with zero-length content, as possibly created by the OSX / Windows 7 WebDAV clients during an upload, and only if policy.content.update.ignoreEmpty=true (the default) - OnContentUpdateRuleTrigger removed. Now all content property updates are handled by OnPropertyUpdateRuleTrigger so that we can have this subtle treatment of 'empty' content properties. - Reverted ALF-14744 changes to RuleServiceCoverageTest and added new tests for 'empty' content and ASPECT_NO_CONTENT - Updated RuleTriggerTest in line with changes 43842: Merged V4.0-BUG-FIX to V3.4-BUG-FIX 33387: ALF-12492 - Email with empty subject sent to Alfresco by SMTP cause Null pointer Exception 43843: ALF-16717: Merged V4.1-BUG-FIX to V3.4-BUG-FIX 43314: ALF-16575 - Email server does not accept email where Subject ends with a period git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@43914 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- config/alfresco/action-services-context.xml | 12 +- config/alfresco/cache-context.xml | 6 +- config/alfresco/cmis-api-context.xml | 5 - config/alfresco/dao/dao-context.xml | 2 - .../messages/patch-service_ru.properties | 3 +- .../alfresco/patch/patch-services-context.xml | 6 + config/alfresco/rule-services-context.xml | 16 +- .../imap/default/imap-server-context.xml | 3 + config/alfresco/tx-cache-context.xml | 46 +-- .../changelog/CMISChangeLogDataExtractor.java | 124 --------- .../changelog/CMISChangeLogServiceImpl.java | 1 + .../opencmis/AlfrescoCmisServiceImpl.java | 11 +- .../opencmis/CMISChangeLogDataExtractor.java | 10 +- .../org/alfresco/opencmis/CMISConnector.java | 11 +- .../alfresco/opencmis/CMISNodeInfoImpl.java | 17 +- .../ImageTransformActionExecuter.java | 80 ++---- .../action/executer/MailActionExecuter.java | 69 +++-- .../executer/TransformActionExecuter.java | 25 +- .../feed/ErrorProneActionExecutor.java | 97 +++++++ .../feed/ErrorProneUserNotifier.java | 94 +++++++ .../activities/feed/FeedNotifierImpl.java | 25 +- .../activities/feed/FeedNotifierTest.java | 243 ++++++++++++++++ .../repo/cache/TransactionalCache.java | 45 ++- .../repo/domain/node/AbstractNodeDAOImpl.java | 48 ++++ .../permissions/AbstractAclCrudDAOImpl.java | 16 +- .../repo/domain/permissions/AclCrudDAO.java | 6 + .../repo/domain/permissions/AclDAO.java | 6 + .../repo/domain/permissions/AclDAOImpl.java | 127 +++------ .../repo/domain/permissions/AclEntity.java | 13 +- .../node/ContentModelFormProcessor.java | 40 ++- .../repo/imap/AlfrescoImapFolder.java | 4 +- .../repo/imap/AttachmentsExtractor.java | 28 +- .../org/alfresco/repo/imap/ImapService.java | 2 + .../alfresco/repo/imap/ImapServiceImpl.java | 23 ++ .../ml/MultilingualContentServiceImpl.java | 36 ++- .../MultilingualContentServiceImplTest.java | 53 ++++ .../alfresco/repo/node/NodeBulkLoader.java | 10 + .../repo/rule/RuleServiceCoverageTest.java | 49 +++- .../alfresco/repo/rule/RuleTypeImplTest.java | 25 +- .../ruletrigger/CreateNodeRuleTrigger.java | 46 --- .../OnContentUpdateRuleTrigger.java | 158 ----------- .../OnPropertyUpdateRuleTrigger.java | 262 +++++++++++++----- .../rule/ruletrigger/RuleTriggerTest.java | 24 +- .../search/impl/solr/SolrBackupClient.java | 2 +- .../impl/PermissionServiceImpl.java | 51 ++-- .../HomeFolderProviderSynchronizer.java | 15 +- .../repo/solr/SOLRTrackingComponentImpl.java | 18 +- 47 files changed, 1253 insertions(+), 760 deletions(-) delete mode 100644 source/java/org/alfresco/cmis/changelog/CMISChangeLogDataExtractor.java create mode 100644 source/java/org/alfresco/repo/activities/feed/ErrorProneActionExecutor.java create mode 100644 source/java/org/alfresco/repo/activities/feed/ErrorProneUserNotifier.java create mode 100644 source/java/org/alfresco/repo/activities/feed/FeedNotifierTest.java delete mode 100644 source/java/org/alfresco/repo/rule/ruletrigger/OnContentUpdateRuleTrigger.java diff --git a/config/alfresco/action-services-context.xml b/config/alfresco/action-services-context.xml index 249ebbebb0..6ae0233d68 100644 --- a/config/alfresco/action-services-context.xml +++ b/config/alfresco/action-services-context.xml @@ -420,12 +420,6 @@ - - - - - - @@ -493,6 +487,12 @@ + + + false + + + diff --git a/config/alfresco/cache-context.xml b/config/alfresco/cache-context.xml index 9c7f937189..c5c80a225d 100644 --- a/config/alfresco/cache-context.xml +++ b/config/alfresco/cache-context.xml @@ -323,13 +323,13 @@ - + - + @@ -339,7 +339,7 @@ - + diff --git a/config/alfresco/cmis-api-context.xml b/config/alfresco/cmis-api-context.xml index 568a337583..a432110c34 100644 --- a/config/alfresco/cmis-api-context.xml +++ b/config/alfresco/cmis-api-context.xml @@ -117,11 +117,6 @@ - - - - - diff --git a/config/alfresco/dao/dao-context.xml b/config/alfresco/dao/dao-context.xml index 0a4dbec18e..881888fdc7 100644 --- a/config/alfresco/dao/dao-context.xml +++ b/config/alfresco/dao/dao-context.xml @@ -287,8 +287,6 @@ - - diff --git a/config/alfresco/messages/patch-service_ru.properties b/config/alfresco/messages/patch-service_ru.properties index cd07c4913d..a13321dbbb 100755 --- a/config/alfresco/messages/patch-service_ru.properties +++ b/config/alfresco/messages/patch-service_ru.properties @@ -488,4 +488,5 @@ patch.increaseColumnSizeActiviti.description=ALF-14983 : Upgrade scripts to incr patch.removeColumnActiviti.description=ALF-16038 : DB2: Upgrade script to remove ALFUSER.ACT_HI_ACTINST.OWNER_ patch.upgradeToActiviti5-10.description=Upgraded Activiti tables to 5.10 version -patch.addActivtiIndexHistoricActivity.description=Additional index for activiti on historic activity (PROC_INST_ID_ and ACTIVITY_ID_) \ No newline at end of file +patch.addActivtiIndexHistoricActivity.description=Additional index for activiti on historic activity (PROC_INST_ID_ and ACTIVITY_ID_) +patch.renameConstraintActiviti.description=ALF-15828 : DB2: Upgrade script to rename ACT_HI_PROCINST.PROC_INST_ID_ index \ No newline at end of file diff --git a/config/alfresco/patch/patch-services-context.xml b/config/alfresco/patch/patch-services-context.xml index b63443dc13..8c34c8b220 100644 --- a/config/alfresco/patch/patch-services-context.xml +++ b/config/alfresco/patch/patch-services-context.xml @@ -2484,6 +2484,12 @@ 0 5010 5011 + + + + + + diff --git a/config/alfresco/rule-services-context.xml b/config/alfresco/rule-services-context.xml index 8e3bb6cecd..02bfa6faa4 100644 --- a/config/alfresco/rule-services-context.xml +++ b/config/alfresco/rule-services-context.xml @@ -74,7 +74,6 @@ - @@ -115,6 +114,9 @@ + + ${policy.content.update.ignoreEmpty} + @@ -153,16 +155,14 @@ - - - false - - - - + true + + + ${policy.content.update.ignoreEmpty} + diff --git a/config/alfresco/subsystems/imap/default/imap-server-context.xml b/config/alfresco/subsystems/imap/default/imap-server-context.xml index c1fdf8b9ad..af0a1bb00d 100644 --- a/config/alfresco/subsystems/imap/default/imap-server-context.xml +++ b/config/alfresco/subsystems/imap/default/imap-server-context.xml @@ -115,6 +115,9 @@ + + + diff --git a/config/alfresco/tx-cache-context.xml b/config/alfresco/tx-cache-context.xml index 13db975ea7..0f7081154b 100644 --- a/config/alfresco/tx-cache-context.xml +++ b/config/alfresco/tx-cache-context.xml @@ -428,6 +428,7 @@ + @@ -507,51 +508,6 @@ - - - - - - - - org.alfresco.compiledModelsTransactionalCache - - - - - - - - - - - - - - - org.alfresco.prefixesTransactionalCache - - - - - - - - - - - - - - - org.alfresco.webScriptsRegistryTransactionalCache - - - - - - - diff --git a/source/java/org/alfresco/cmis/changelog/CMISChangeLogDataExtractor.java b/source/java/org/alfresco/cmis/changelog/CMISChangeLogDataExtractor.java deleted file mode 100644 index fbd3a17acc..0000000000 --- a/source/java/org/alfresco/cmis/changelog/CMISChangeLogDataExtractor.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (C) 2005-2010 Alfresco Software Limited. - * - * This file is part of Alfresco - * - * Alfresco is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Alfresco is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Alfresco. If not, see . - */ -package org.alfresco.cmis.changelog; - -import java.io.Serializable; -import java.util.HashMap; - -import org.alfresco.cmis.CMISDictionaryModel; -import org.alfresco.cmis.CMISInvalidArgumentException; -import org.alfresco.cmis.CMISServices; -import org.alfresco.cmis.CMISTypeDefinition; -import org.alfresco.cmis.CMISTypeId; -import org.alfresco.repo.audit.extractor.AbstractDataExtractor; -import org.alfresco.service.cmr.model.FileInfo; -import org.alfresco.service.cmr.repository.ChildAssociationRef; -import org.alfresco.service.cmr.repository.NodeRef; - -/** - * An extractor that allows to filter data using the following rule: - * Audit records should only be created for items in the CMIS domain model. - * - * @author Stas Sokolovsky - */ -public class CMISChangeLogDataExtractor extends AbstractDataExtractor -{ - private CMISServices cmisService; - - public static final String KEY_NODE_REF = "nodeRef"; - public static final String KEY_OBJECT_ID = "objectId"; - - /** - * Extracts relevant node refs and Ids from auditing data - * - * @see org.alfresco.repo.audit.extractor.DataExtractor.extractData(java.io.Serializable) - */ - public Serializable extractData(Serializable value) throws Throwable - { - NodeRef nodeRef = getNodeRef(value); - HashMap result = new HashMap(5); - result.put(KEY_NODE_REF, nodeRef); - // Support version nodes by recording the object ID - result.put(KEY_OBJECT_ID, cmisService.getProperty(nodeRef, CMISDictionaryModel.PROP_OBJECT_ID)); - return result; - } - - /** - * @return Returns true if items in the CMIS domain model - * @see org.alfresco.repo.audit.extractor.DataExtractor.isSupported(java.io.Serializable) - */ - public boolean isSupported(Serializable data) - { - if (data != null) - { - NodeRef nodeRef = getNodeRef(data); - if (nodeRef != null) - { - try - { - CMISTypeDefinition typeDef = cmisService.getTypeDefinition(nodeRef); - if (typeDef != null) - { - CMISTypeId typeId = typeDef.getBaseType().getTypeId(); - return typeId.equals(CMISDictionaryModel.DOCUMENT_TYPE_ID) || typeId.equals(CMISDictionaryModel.FOLDER_TYPE_ID); - } - } - catch (CMISInvalidArgumentException e) - { - // Ignore and return false - } - } - } - return false; - } - - /** - * Gets the NodeRef from auditing data - * - * @param data audit data - * @return Node Reference - */ - private NodeRef getNodeRef(Serializable data) - { - NodeRef nodeRef = null; - if (data instanceof ChildAssociationRef) - { - nodeRef = ((ChildAssociationRef) data).getChildRef(); - } - if (data instanceof FileInfo) - { - nodeRef = ((FileInfo) data).getNodeRef(); - } - else if (data instanceof NodeRef) - { - nodeRef = (NodeRef) data; - } - return nodeRef; - } - - /** - * Set the CMIS service - * - * @param cmisService CMIS service - */ - public void setCmisService(CMISServices cmisService) - { - this.cmisService = cmisService; - } -} diff --git a/source/java/org/alfresco/cmis/changelog/CMISChangeLogServiceImpl.java b/source/java/org/alfresco/cmis/changelog/CMISChangeLogServiceImpl.java index d6dfd0def8..5524ab90d8 100644 --- a/source/java/org/alfresco/cmis/changelog/CMISChangeLogServiceImpl.java +++ b/source/java/org/alfresco/cmis/changelog/CMISChangeLogServiceImpl.java @@ -34,6 +34,7 @@ import org.alfresco.cmis.CMISChangeLogService; import org.alfresco.cmis.CMISChangeType; import org.alfresco.cmis.CMISInvalidArgumentException; import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.opencmis.CMISChangeLogDataExtractor; import org.alfresco.service.cmr.audit.AuditQueryParameters; import org.alfresco.service.cmr.audit.AuditService; import org.alfresco.service.cmr.audit.AuditService.AuditQueryCallback; diff --git a/source/java/org/alfresco/opencmis/AlfrescoCmisServiceImpl.java b/source/java/org/alfresco/opencmis/AlfrescoCmisServiceImpl.java index d724d437f7..372b0c48b4 100644 --- a/source/java/org/alfresco/opencmis/AlfrescoCmisServiceImpl.java +++ b/source/java/org/alfresco/opencmis/AlfrescoCmisServiceImpl.java @@ -248,12 +248,17 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr protected CMISNodeInfoImpl createNodeInfo(NodeRef nodeRef) { - CMISNodeInfoImpl result = connector.createNodeInfo(nodeRef); + return createNodeInfo(nodeRef, null); + } + + protected CMISNodeInfoImpl createNodeInfo(NodeRef nodeRef, VersionHistory versionHistory) + { + CMISNodeInfoImpl result = connector.createNodeInfo(nodeRef, versionHistory); nodeInfoMap.put(result.getObjectId(), result); return result; } - + protected CMISNodeInfo createNodeInfo(AssociationRef assocRef) { CMISNodeInfoImpl result = connector.createNodeInfo(assocRef); @@ -2004,7 +2009,7 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr // convert the version history for (Version version : versionHistory.getAllVersions()) { - CMISNodeInfo versionInfo = createNodeInfo(version.getFrozenStateNodeRef()); + CMISNodeInfo versionInfo = createNodeInfo(version.getFrozenStateNodeRef(), versionHistory); result.add( connector.createCMISObject( diff --git a/source/java/org/alfresco/opencmis/CMISChangeLogDataExtractor.java b/source/java/org/alfresco/opencmis/CMISChangeLogDataExtractor.java index 5579c56277..e97919a38d 100644 --- a/source/java/org/alfresco/opencmis/CMISChangeLogDataExtractor.java +++ b/source/java/org/alfresco/opencmis/CMISChangeLogDataExtractor.java @@ -26,6 +26,7 @@ import org.alfresco.opencmis.dictionary.CMISPropertyAccessor; import org.alfresco.opencmis.dictionary.TypeDefinitionWrapper; import org.alfresco.repo.audit.extractor.AbstractDataExtractor; import org.alfresco.service.cmr.model.FileInfo; +import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.namespace.QName; @@ -100,10 +101,15 @@ public class CMISChangeLogDataExtractor extends AbstractDataExtractor private NodeRef getNodeRef(Serializable data) { NodeRef nodeRef = null; - if (data instanceof FileInfo) + if (data instanceof ChildAssociationRef) + { + nodeRef = ((ChildAssociationRef) data).getChildRef(); + } + else if (data instanceof FileInfo) { nodeRef = ((FileInfo) data).getNodeRef(); - } else if (data instanceof NodeRef) + } + else if (data instanceof NodeRef) { nodeRef = (NodeRef) data; } diff --git a/source/java/org/alfresco/opencmis/CMISConnector.java b/source/java/org/alfresco/opencmis/CMISConnector.java index cfcb8bbfe6..237831909a 100644 --- a/source/java/org/alfresco/opencmis/CMISConnector.java +++ b/source/java/org/alfresco/opencmis/CMISConnector.java @@ -104,6 +104,7 @@ import org.alfresco.service.cmr.security.AuthenticationService; import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.cmr.site.SiteInfo; import org.alfresco.service.cmr.site.SiteService; +import org.alfresco.service.cmr.version.VersionHistory; import org.alfresco.service.cmr.version.VersionService; import org.alfresco.service.cmr.version.VersionType; import org.alfresco.service.descriptor.Descriptor; @@ -844,7 +845,15 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen */ public CMISNodeInfoImpl createNodeInfo(NodeRef nodeRef) { - return new CMISNodeInfoImpl(this, nodeRef); + return createNodeInfo(nodeRef, null); + } + + /** + * Creates an object info object. + */ + public CMISNodeInfoImpl createNodeInfo(NodeRef nodeRef, VersionHistory versionHistory) + { + return new CMISNodeInfoImpl(this, nodeRef, versionHistory); } /** diff --git a/source/java/org/alfresco/opencmis/CMISNodeInfoImpl.java b/source/java/org/alfresco/opencmis/CMISNodeInfoImpl.java index 529421db4c..19e158a13b 100644 --- a/source/java/org/alfresco/opencmis/CMISNodeInfoImpl.java +++ b/source/java/org/alfresco/opencmis/CMISNodeInfoImpl.java @@ -94,6 +94,15 @@ public class CMISNodeInfoImpl implements CMISNodeInfo analyseObjectId(); } + public CMISNodeInfoImpl(CMISConnector connector, NodeRef nodeRef, VersionHistory versionHistory) + { + this.connector = connector; + this.nodeRef = nodeRef; + this.versionHistory = versionHistory; + + analyseNodeRef(); + } + public CMISNodeInfoImpl(CMISConnector connector, NodeRef nodeRef) { this.connector = connector; @@ -115,10 +124,10 @@ public class CMISNodeInfoImpl implements CMISNodeInfo return objecVariant != CMISObjectVariant.VERSION; } - protected void analyseVersionNode(NodeRef nodeRef) + protected void analyseVersionNode() { // check version - versionHistory = connector.getVersionService().getVersionHistory(nodeRef); + versionHistory = getVersionHistory(); if (versionHistory == null) { objecVariant = CMISObjectVariant.CURRENT_VERSION; @@ -301,7 +310,7 @@ public class CMISNodeInfoImpl implements CMISNodeInfo { // the version label refers to a specific non-head version, find the nodeRef // of the version node from the version history - versionHistory = connector.getVersionService().getVersionHistory(nodeRef); + versionHistory = getVersionHistory(); if (versionHistory == null) { // unexpected null versionHistory, assume not versioned @@ -420,7 +429,7 @@ public class CMISNodeInfoImpl implements CMISNodeInfo // check version if(connector.getVersionService().isAVersion(nodeRef)) { - analyseVersionNode(nodeRef); + analyseVersionNode(); } else { diff --git a/source/java/org/alfresco/repo/action/executer/ImageTransformActionExecuter.java b/source/java/org/alfresco/repo/action/executer/ImageTransformActionExecuter.java index c54ed8c11f..eda401308a 100644 --- a/source/java/org/alfresco/repo/action/executer/ImageTransformActionExecuter.java +++ b/source/java/org/alfresco/repo/action/executer/ImageTransformActionExecuter.java @@ -18,49 +18,36 @@ */ package org.alfresco.repo.action.executer; -import java.util.ArrayList; import java.util.List; +import org.alfresco.model.ContentModel; import org.alfresco.repo.action.ParameterDefinitionImpl; -import org.alfresco.repo.content.transform.ContentTransformer; -import org.alfresco.repo.content.transform.TransformerDebug; import org.alfresco.repo.content.transform.magick.ImageTransformationOptions; import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.ParameterDefinition; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.service.cmr.repository.ContentReader; -import org.alfresco.service.cmr.repository.ContentWriter; -import org.alfresco.service.cmr.repository.NoTransformerException; import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.TransformationOptions; /** - * Transfor action executer + * Transform action executer. Modified original code to use any transformer rather than just + * ImageMagick. The original, allowed additional command line options to be supplied (added for + * backward compatibility in 15/04/2008). Now removed. Did not find any internal usage (22/11/2012) but it + * is possible that there may be some in Javascript calling ScriptNode or custom code using + * the context passed to the ImageRenderingEngine. That said, it will almost always be ImagMagick + * that does the transform so it will still be used. These command line options were to overcome issues with + * ImageMagick. Other transformers will just ignore them. * * @author Roy Wetherall */ public class ImageTransformActionExecuter extends TransformActionExecuter { - private TransformerDebug transformerDebug; - /** * Action constants */ public static final String NAME = "transform-image"; public static final String PARAM_CONVERT_COMMAND = "convert-command"; - private ContentTransformer imageMagickContentTransformer; - - /** - * Set the image magick content transformer - * - * @param imageMagickContentTransformer - * the conten transformer - */ - public void setImageMagickContentTransformer(ContentTransformer imageMagickContentTransformer) - { - this.imageMagickContentTransformer = imageMagickContentTransformer; - } - /** * Add parameter definitions */ @@ -71,48 +58,17 @@ public class ImageTransformActionExecuter extends TransformActionExecuter paramList.add(new ParameterDefinitionImpl(PARAM_CONVERT_COMMAND, DataTypeDefinition.TEXT, false, getParamDisplayLabel(PARAM_CONVERT_COMMAND))); } - public void setTransformerDebug(TransformerDebug transformerDebug) + @Override + protected TransformationOptions newTransformationOptions(Action ruleAction, NodeRef sourceNodeRef) { - this.transformerDebug = transformerDebug; - } - - /** - * @see org.alfresco.repo.action.executer.TransformActionExecuter#doTransform(org.alfresco.service.cmr.action.Action, org.alfresco.service.cmr.repository.ContentReader, org.alfresco.service.cmr.repository.ContentWriter) - */ - protected void doTransform(Action ruleAction, - NodeRef sourceNodeRef, ContentReader contentReader, - NodeRef destinationNodeRef, ContentWriter contentWriter) - { - // Try and transform the content + ImageTransformationOptions options = new ImageTransformationOptions(); + options.setSourceNodeRef(sourceNodeRef); + options.setSourceContentProperty(ContentModel.PROP_NAME); + options.setTargetContentProperty(ContentModel.PROP_NAME); + String convertCommand = (String) ruleAction.getParameterValue(PARAM_CONVERT_COMMAND); - // create some options for the transform - ImageTransformationOptions imageOptions = new ImageTransformationOptions(); - imageOptions.setCommandOptions(convertCommand); - imageOptions.setSourceNodeRef(sourceNodeRef); + options.setCommandOptions(convertCommand); - // check if the transformer is going to work, i.e. is available - String sourceMimetype = contentReader.getMimetype(); - String targetMimetype = contentWriter.getMimetype(); - long sourceSize = contentReader.getSize(); - try - { - transformerDebug.pushAvailable(contentReader.getContentUrl(), sourceMimetype, targetMimetype, imageOptions); - List transformers = new ArrayList(1); - if (imageMagickContentTransformer.isTransformable(sourceMimetype, contentReader.getSize(), targetMimetype, imageOptions)) - { - transformers.add(imageMagickContentTransformer); - } - transformerDebug.availableTransformers(transformers, sourceSize, "ImageTransformActionExecuter.doTransform(...)"); - if (transformers.size() == 0) - { - throw new NoTransformerException(sourceMimetype, targetMimetype); - } - - imageMagickContentTransformer.transform(contentReader, contentWriter, imageOptions); - } - finally - { - transformerDebug.popAvailable(); - } + return options; } } diff --git a/source/java/org/alfresco/repo/action/executer/MailActionExecuter.java b/source/java/org/alfresco/repo/action/executer/MailActionExecuter.java index dcea0fcd1c..8ad0c89753 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-2010 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -39,8 +39,8 @@ import org.alfresco.repo.template.I18NMessageMethod; import org.alfresco.repo.template.TemplateNode; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.RetryingTransactionHelper; -import org.alfresco.repo.transaction.TransactionListenerAdapter; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.repo.transaction.TransactionListenerAdapter; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.ParameterDefinition; @@ -301,6 +301,13 @@ public class MailActionExecuter extends ActionExecuterAbstractBase final Action ruleAction, final NodeRef actionedUponNodeRef) { + MimeMessageHelper message = null; + if (!testMode && validNodeRefIfPresent(actionedUponNodeRef)) + { + message = prepareEmail(ruleAction, actionedUponNodeRef); + } + final MimeMessageHelper finalMessage = message; + if (sendAfterCommit(ruleAction)) { AlfrescoTransactionSupport.bindListener(new TransactionListenerAdapter() @@ -314,9 +321,9 @@ public class MailActionExecuter extends ActionExecuterAbstractBase @Override public Void execute() throws Throwable { - if (validNodeRefIfPresent(actionedUponNodeRef)) + if (finalMessage != null) { - prepareAndSendEmail(ruleAction, actionedUponNodeRef); + sendEmail(ruleAction, actionedUponNodeRef, finalMessage); } return null; } @@ -328,7 +335,7 @@ public class MailActionExecuter extends ActionExecuterAbstractBase { if (validNodeRefIfPresent(actionedUponNodeRef)) { - prepareAndSendEmail(ruleAction, actionedUponNodeRef); + sendEmail(ruleAction, actionedUponNodeRef, finalMessage); } } } @@ -356,9 +363,14 @@ public class MailActionExecuter extends ActionExecuterAbstractBase return sendAfterCommit == null ? false : sendAfterCommit.booleanValue(); } - private void prepareAndSendEmail(final Action ruleAction, final NodeRef actionedUponNodeRef) + public MimeMessageHelper prepareEmail(final Action ruleAction, final NodeRef actionedUponNodeRef) { - // Create the mime mail message + // Create the mime mail message. + // Hack: using an array here to get around the fact that inner classes aren't closures. + // The MimeMessagePreparator.prepare() signature does not allow us to return a value and yet + // we can't set a result on a bare, non-final object reference due to Java language restrictions. + final MimeMessageHelper[] messageRef = new MimeMessageHelper[1]; + MimeMessagePreparator mailPreparer = new MimeMessagePreparator() { @SuppressWarnings("unchecked") @@ -369,7 +381,7 @@ public class MailActionExecuter extends ActionExecuterAbstractBase logger.debug(ruleAction.getParameterValues()); } - MimeMessageHelper message = new MimeMessageHelper(mimeMessage); + messageRef[0] = new MimeMessageHelper(mimeMessage); // set header encoding if one has been supplied if (headerEncoding != null && headerEncoding.length() != 0) @@ -381,7 +393,7 @@ public class MailActionExecuter extends ActionExecuterAbstractBase String to = (String)ruleAction.getParameterValue(PARAM_TO); if (to != null && to.length() != 0) { - message.setTo(to); + messageRef[0].setTo(to); } else { @@ -449,7 +461,7 @@ public class MailActionExecuter extends ActionExecuterAbstractBase if(recipients.size() > 0) { - message.setTo(recipients.toArray(new String[recipients.size()])); + messageRef[0].setTo(recipients.toArray(new String[recipients.size()])); } else { @@ -487,7 +499,7 @@ public class MailActionExecuter extends ActionExecuterAbstractBase { logger.debug("from specified as a parameter, from:" + from); } - message.setFrom(from); + messageRef[0].setFrom(from); } else { @@ -504,12 +516,12 @@ public class MailActionExecuter extends ActionExecuterAbstractBase { logger.debug("looked up email address for :" + fromPerson + " email from " + fromActualUser); } - message.setFrom(fromActualUser); + messageRef[0].setFrom(fromActualUser); } else { // from system or user does not have email address - message.setFrom(fromDefaultAddress); + messageRef[0].setFrom(fromDefaultAddress); } } @@ -521,14 +533,14 @@ public class MailActionExecuter extends ActionExecuterAbstractBase logger.debug("from not enabled - sending from default address:" + fromDefaultAddress); } // from is not enabled. - message.setFrom(fromDefaultAddress); + messageRef[0].setFrom(fromDefaultAddress); } // set subject line - message.setSubject((String)ruleAction.getParameterValue(PARAM_SUBJECT)); + messageRef[0].setSubject((String)ruleAction.getParameterValue(PARAM_SUBJECT)); // See if an email template has been specified String text = null; @@ -587,24 +599,41 @@ public class MailActionExecuter extends ActionExecuterAbstractBase if (text != null) { - message.setText(text, isHTML); + messageRef[0].setText(text, isHTML); } } }; + MimeMessage mimeMessage = javaMailSender.createMimeMessage(); + try + { + mailPreparer.prepare(mimeMessage); + } catch (Exception e) + { + // We're forced to catch java.lang.Exception here. Urgh. + if (logger.isInfoEnabled()) + { + logger.warn("Unable to prepare mail message. Skipping.", e); + } + } + return messageRef[0]; + } + + private void sendEmail(final Action ruleAction, final NodeRef actionedUponNodeRef, MimeMessageHelper preparedMessage) + { try { // Send the message unless we are in "testMode" - if(!testMode) + if(!testMode && preparedMessage != null) { - javaMailSender.send(mailPreparer); + javaMailSender.send(preparedMessage.getMimeMessage()); } - else + else if (validNodeRefIfPresent(actionedUponNodeRef)) { try { MimeMessage mimeMessage = javaMailSender.createMimeMessage(); - mailPreparer.prepare(mimeMessage); + prepareEmail(ruleAction, actionedUponNodeRef); lastTestMessage = mimeMessage; } catch(Exception e) { System.err.println(e); diff --git a/source/java/org/alfresco/repo/action/executer/TransformActionExecuter.java b/source/java/org/alfresco/repo/action/executer/TransformActionExecuter.java index 669580f5a8..588b71fb14 100644 --- a/source/java/org/alfresco/repo/action/executer/TransformActionExecuter.java +++ b/source/java/org/alfresco/repo/action/executer/TransformActionExecuter.java @@ -81,6 +81,11 @@ public class TransformActionExecuter extends ActionExecuterAbstractBase private CopyService copyService; private MimetypeService mimetypeService; + /** + * Properties (needed to avoid changing method signatures) + */ + protected TransformationOptions options; + /** * Set the mime type service */ @@ -172,8 +177,7 @@ public class TransformActionExecuter extends ActionExecuterAbstractBase throw new RuleServiceException(CONTENT_READER_NOT_FOUND_MESSAGE); } - TransformationOptions options = new TransformationOptions(); - options.setSourceNodeRef(actionedUponNodeRef); + options = newTransformationOptions(ruleAction, actionedUponNodeRef); if (null == contentService.getTransformer(contentReader.getContentUrl(), contentReader.getMimetype(), contentReader.getSize(), mimeType, options)) { throw new RuleServiceException(String.format(TRANSFORMER_NOT_EXISTS_MESSAGE_PATTERN, contentReader.getMimetype(), mimeType)); @@ -243,7 +247,6 @@ public class TransformActionExecuter extends ActionExecuterAbstractBase } } - boolean newCopy = false; if (copyNodeRef == null) { // Copy the content node @@ -253,11 +256,7 @@ public class TransformActionExecuter extends ActionExecuterAbstractBase destinationAssocTypeQName, destinationAssocQName, false); - newCopy = true; - } - - if (newCopy == true) - { + // Adjust the name of the copy nodeService.setProperty(copyNodeRef, ContentModel.PROP_NAME, newName); String originalTitle = (String)nodeService.getProperty(actionedUponNodeRef, ContentModel.PROP_TITLE); @@ -282,6 +281,7 @@ public class TransformActionExecuter extends ActionExecuterAbstractBase // TODO: Check failure patterns for actions. try { + options.setTargetNodeRef(copyNodeRef); doTransform(ruleAction, actionedUponNodeRef, contentReader, copyNodeRef, contentWriter); } catch(NoTransformerException e) @@ -296,6 +296,11 @@ public class TransformActionExecuter extends ActionExecuterAbstractBase throw new RuleServiceException(TRANSFORMING_ERROR_MESSAGE + e.getMessage()); } } + } + + protected TransformationOptions newTransformationOptions(Action ruleAction, NodeRef sourceNodeRef) + { + return new TransformationOptions(sourceNodeRef, ContentModel.PROP_NAME, null, ContentModel.PROP_NAME); } /** @@ -306,10 +311,6 @@ public class TransformActionExecuter extends ActionExecuterAbstractBase NodeRef destinationNodeRef, ContentWriter contentWriter) { - // Transformation options - TransformationOptions options = new TransformationOptions( - sourceNodeRef, ContentModel.PROP_NAME, destinationNodeRef, ContentModel.PROP_NAME); - // transform - will throw NoTransformerException if there are no transformers this.contentService.transform(contentReader, contentWriter, options); } diff --git a/source/java/org/alfresco/repo/activities/feed/ErrorProneActionExecutor.java b/source/java/org/alfresco/repo/activities/feed/ErrorProneActionExecutor.java new file mode 100644 index 0000000000..1290a6c7c6 --- /dev/null +++ b/source/java/org/alfresco/repo/activities/feed/ErrorProneActionExecutor.java @@ -0,0 +1,97 @@ +package org.alfresco.repo.activities.feed; + +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.action.ParameterDefinitionImpl; +import org.alfresco.repo.action.executer.ActionExecuterAbstractBase; +import org.alfresco.repo.action.executer.TestModeable; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ParameterDefinition; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.repository.NodeRef; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.InitializingBean; + +public class ErrorProneActionExecutor extends ActionExecuterAbstractBase + implements InitializingBean, TestModeable +{ + private static Log logger = LogFactory.getLog(ErrorProneActionExecutor.class); + + public static final String PARAM_FAILING_PERSON_NODEREF = "failingPersonNodeRef"; + public static final String PARAM_PERSON_NODEREF = "personNodeRef"; + public static final String PARAM_USERNAME = "userName"; + + public static final String NAME = "errorProneActionExecutor"; + + // count of number of successful notifications + private AtomicInteger numSuccessful = new AtomicInteger(); + + // count of number of failed notifications + private AtomicInteger numFailed = new AtomicInteger(); + + public int getNumSuccess() + { + return numSuccessful.get(); + } + + public int getNumFailed() + { + return numFailed.get(); + } + + /** + * Send an email message + * + * @throws AlfrescoRuntimeExeption + */ + @Override + protected void executeImpl( + final Action ruleAction, + final NodeRef actionedUponNodeRef) + { + NodeRef failingPersonNodeRef = (NodeRef)ruleAction.getParameterValue(PARAM_FAILING_PERSON_NODEREF); + NodeRef personNodeRef = (NodeRef)ruleAction.getParameterValue(PARAM_PERSON_NODEREF); + String userName = (String)ruleAction.getParameterValue(PARAM_USERNAME); + + System.out.println("userName = " + userName); + + if(personNodeRef.equals(failingPersonNodeRef)) + { + numFailed.incrementAndGet(); + throw new AlfrescoRuntimeException(""); + } + + numSuccessful.incrementAndGet(); + } + + /** + * Add the parameter definitions + */ + @Override + protected void addParameterDefinitions(List paramList) + { + paramList.add(new ParameterDefinitionImpl(PARAM_FAILING_PERSON_NODEREF, DataTypeDefinition.NODE_REF, true, "Failing Person NodeRef")); + paramList.add(new ParameterDefinitionImpl(PARAM_PERSON_NODEREF, DataTypeDefinition.NODE_REF, true, "Person NodeRef")); + paramList.add(new ParameterDefinitionImpl(PARAM_USERNAME, DataTypeDefinition.TEXT, true, "Username")); + } + + @Override + public boolean isTestMode() + { + return true; + } + + @Override + public void setTestMode(boolean testMode) + { + } + + @Override + public void afterPropertiesSet() throws Exception + { + + } +} diff --git a/source/java/org/alfresco/repo/activities/feed/ErrorProneUserNotifier.java b/source/java/org/alfresco/repo/activities/feed/ErrorProneUserNotifier.java new file mode 100644 index 0000000000..7feb812d46 --- /dev/null +++ b/source/java/org/alfresco/repo/activities/feed/ErrorProneUserNotifier.java @@ -0,0 +1,94 @@ +package org.alfresco.repo.activities.feed; + +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; +import org.alfresco.service.namespace.QName; + +public class ErrorProneUserNotifier extends AbstractUserNotifier +{ +// private AtomicInteger numSuccessful = new AtomicInteger(); +// private AtomicInteger numFailed = new AtomicInteger(); + + private NodeRef failingPersonNodeRef; + private ActionService actionService; + + public ErrorProneUserNotifier(NodeRef failingPersonNodeRef) + { + this.failingPersonNodeRef = failingPersonNodeRef; + } + + public void setActionService(ActionService actionService) + { + this.actionService = actionService; + } + + @Override + protected boolean skipUser(NodeRef personNodeRef) + { + return false; + } + + @Override + protected Long getFeedId(NodeRef personNodeRef) + { + Map personProps = nodeService.getProperties(personNodeRef); + + // where did we get up to ? + Long emailFeedDBID = (Long)personProps.get(ContentModel.PROP_EMAIL_FEED_ID); + if (emailFeedDBID != null) + { + // increment min feed id + emailFeedDBID++; + } + else + { + emailFeedDBID = -1L; + } + + return emailFeedDBID; + } + +// public int getNumSuccess() +// { +// return numSuccessful.get(); +// } +// +// public int getNumFailed() +// { +// return numFailed.get(); +// } + + @Override + protected void notifyUser(NodeRef personNodeRef, String subjectText, + Map model, NodeRef templateNodeRef) + { +// super.notifyUser(personNodeRef, subjectText, model, templateNodeRef); + + String userName = (String)nodeService.getProperty(personNodeRef, ContentModel.PROP_USERNAME); + + Action action = actionService.createAction(ErrorProneActionExecutor.NAME); + + action.setParameterValue(ErrorProneActionExecutor.PARAM_FAILING_PERSON_NODEREF, failingPersonNodeRef); + action.setParameterValue(ErrorProneActionExecutor.PARAM_PERSON_NODEREF, personNodeRef); + action.setParameterValue(ErrorProneActionExecutor.PARAM_USERNAME, userName); + + actionService.executeAction(action, null); +// String userName = (String)nodeService.getProperty(personNodeRef, ContentModel.PROP_USERNAME); +// +// System.out.println("userName = " + userName); +// +// if(personNodeRef.equals(failingPersonNodeRef)) +// { +// numFailed.incrementAndGet(); +// throw new AlfrescoRuntimeException(""); +// } +// +// numSuccessful.incrementAndGet(); + } +} diff --git a/source/java/org/alfresco/repo/activities/feed/FeedNotifierImpl.java b/source/java/org/alfresco/repo/activities/feed/FeedNotifierImpl.java index 68a3d26d14..4055120a9a 100644 --- a/source/java/org/alfresco/repo/activities/feed/FeedNotifierImpl.java +++ b/source/java/org/alfresco/repo/activities/feed/FeedNotifierImpl.java @@ -17,6 +17,7 @@ import org.alfresco.repo.lock.JobLockService; import org.alfresco.repo.lock.LockAcquisitionException; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.cmr.admin.RepoAdminService; import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.repository.InvalidNodeRefException; @@ -88,7 +89,7 @@ public class FeedNotifierImpl implements FeedNotifier, ApplicationContextAware this.batchSize = batchSize; } - public void setUserNotifier(UserNotifier userNotifier) + public void setUserNotifier(UserNotifier userNotifier) { this.userNotifier = userNotifier; } @@ -165,6 +166,11 @@ public class FeedNotifierImpl implements FeedNotifier, ApplicationContextAware } String lockToken = getLock(LOCK_TTL); + if (lockToken == null) + { + logger.info("Can't get lock. Assume multiple feed notifiers..."); + return; + } try { @@ -281,6 +287,21 @@ public class FeedNotifierImpl implements FeedNotifier, ApplicationContextAware public void process(final PersonInfo person) throws Throwable { + final RetryingTransactionHelper txHelper = transactionService.getRetryingTransactionHelper(); + txHelper.setMaxRetries(0); + + txHelper.doInTransaction(new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + processInternal(person); + return null; + } + }, false, true); + } + + private void processInternal(final PersonInfo person) throws Throwable + { final NodeRef personNodeRef = person.getNodeRef(); try @@ -306,7 +327,7 @@ public class FeedNotifierImpl implements FeedNotifier, ApplicationContextAware // skip this person - eg. no longer exists ? logger.warn("Skip feed notification for user ("+personNodeRef+"): " + inre.getMessage()); } - } + } }; // grab people for the batch processor in chunks of size batchSize diff --git a/source/java/org/alfresco/repo/activities/feed/FeedNotifierTest.java b/source/java/org/alfresco/repo/activities/feed/FeedNotifierTest.java new file mode 100644 index 0000000000..d5ccb0ec60 --- /dev/null +++ b/source/java/org/alfresco/repo/activities/feed/FeedNotifierTest.java @@ -0,0 +1,243 @@ +package org.alfresco.repo.activities.feed; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.activities.post.lookup.PostLookup; +import org.alfresco.repo.domain.activities.ActivityPostDAO; +import org.alfresco.repo.management.subsystems.ChildApplicationContextFactory; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.service.cmr.action.ActionService; +import org.alfresco.service.cmr.activities.ActivityService; +import org.alfresco.service.cmr.admin.RepoAdminService; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.model.FileInfo; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.cmr.site.SiteService; +import org.alfresco.service.cmr.subscriptions.SubscriptionService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.ApplicationContextHelper; +import org.alfresco.util.GUID; +import org.alfresco.util.PropertyMap; +import org.json.JSONObject; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.quartz.Scheduler; +import org.springframework.context.ApplicationContext; +import org.springframework.scheduling.quartz.JobDetailBean; + +/** + * Feed notifier tests. + * + * @author steveglover + * + */ +public class FeedNotifierTest +{ + private static ApplicationContext ctx = null; + + private PersonService personService; + private NodeService nodeService; + private NamespaceService namespaceService; + private SiteService siteService; + private ActivityService activityService; + private RepoAdminService repoAdminService; + private FeedNotifierImpl feedNotifier; + private TransactionService transactionService; + private PostLookup postLookup; + private FeedGenerator feedGenerator; + private FileFolderService fileFolderService; + private SubscriptionService subscriptionService; + private ErrorProneActionExecutor errorProneActionExecutor; + private ActivityPostDAO postDAO; + private ActionService actionService; + + private ErrorProneUserNotifier userNotifier; + + private NodeRef failingPersonNodeRef; + private NodeRef personNodeRef; + private String userName1 = "user1." + GUID.generate(); + private String userName2 = "user2." + GUID.generate(); + + @BeforeClass + public static void init() + { + ApplicationContextHelper.setUseLazyLoading(false); + ApplicationContextHelper.setNoAutoStart(true); + + ctx = ApplicationContextHelper.getApplicationContext(); + } + + @Before + public void before() throws Exception + { + ChildApplicationContextFactory activitiesFeed = (ChildApplicationContextFactory)ctx.getBean("ActivitiesFeed"); + ApplicationContext activitiesFeedCtx = activitiesFeed.getApplicationContext(); + this.feedNotifier = (FeedNotifierImpl)activitiesFeedCtx.getBean("feedNotifier"); + this.activityService = (ActivityService)activitiesFeedCtx.getBean("activityService"); + this.postLookup = (PostLookup)activitiesFeedCtx.getBean("postLookup"); + this.feedGenerator = (FeedGenerator)activitiesFeedCtx.getBean("feedGenerator"); + + Scheduler scheduler = (Scheduler)ctx.getBean("schedulerFactory"); + + JobDetailBean feedGeneratorJobDetail = (JobDetailBean)activitiesFeedCtx.getBean("feedGeneratorJobDetail"); + JobDetailBean postLookupJobDetail = (JobDetailBean)activitiesFeedCtx.getBean("postLookupJobDetail"); + JobDetailBean feedCleanerJobDetail = (JobDetailBean)activitiesFeedCtx.getBean("feedCleanerJobDetail"); + JobDetailBean postCleanerJobDetail = (JobDetailBean)activitiesFeedCtx.getBean("postCleanerJobDetail"); + JobDetailBean feedNotifierJobDetail = (JobDetailBean)activitiesFeedCtx.getBean("feedNotifierJobDetail"); + + // Pause activities jobs so that we aren't competing with their scheduled versions + scheduler.pauseJob(feedGeneratorJobDetail.getName(), feedGeneratorJobDetail.getGroup()); + scheduler.pauseJob(postLookupJobDetail.getName(), postLookupJobDetail.getGroup()); + scheduler.pauseJob(feedCleanerJobDetail.getName(), feedCleanerJobDetail.getGroup()); + scheduler.pauseJob(postCleanerJobDetail.getName(), postCleanerJobDetail.getGroup()); + scheduler.pauseJob(feedNotifierJobDetail.getName(), feedNotifierJobDetail.getGroup()); + + this.personService = (PersonService)ctx.getBean("personService"); + this.nodeService = (NodeService)ctx.getBean("nodeService"); + this.namespaceService = (NamespaceService)ctx.getBean("namespaceService"); + this.siteService = (SiteService)ctx.getBean("siteService"); + this.repoAdminService = (RepoAdminService)ctx.getBean("repoAdminService"); + this.transactionService = (TransactionService)ctx.getBean("transactionService"); + this.postDAO = (ActivityPostDAO)ctx.getBean("postDAO"); + this.fileFolderService = (FileFolderService)ctx.getBean("fileFolderService"); + this.subscriptionService = (SubscriptionService)ctx.getBean("SubscriptionService"); + this.errorProneActionExecutor = (ErrorProneActionExecutor)ctx.getBean("errorProneActionExecutor"); + this.actionService = (ActionService)ctx.getBean("ActionService"); + + // create some users + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + @SuppressWarnings("synthetic-access") + public Void execute() throws Throwable + { + AuthenticationUtil.pushAuthentication(); + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); + + // create person properties + PropertyMap personProps = new PropertyMap(); + personProps.put(ContentModel.PROP_USERNAME, userName1); + personProps.put(ContentModel.PROP_FIRSTNAME, userName1); + personProps.put(ContentModel.PROP_LASTNAME, userName1); + personProps.put(ContentModel.PROP_EMAIL, userName1+"@email.com"); + personNodeRef = personService.createPerson(personProps); + + personProps = new PropertyMap(); + personProps.put(ContentModel.PROP_USERNAME, userName2); + personProps.put(ContentModel.PROP_FIRSTNAME, userName2); + personProps.put(ContentModel.PROP_LASTNAME, userName2); + personProps.put(ContentModel.PROP_EMAIL, userName2+"@email.com"); + failingPersonNodeRef = personService.createPerson(personProps); + + AuthenticationUtil.popAuthentication(); + + return null; + } + }, false, true); + + // and some activities for those users + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + @SuppressWarnings("synthetic-access") + public Void execute() throws Throwable + { + AuthenticationUtil.pushAuthentication(); + AuthenticationUtil.setFullyAuthenticatedUser(userName1); + + NodeRef rootNodeRef = nodeService.getRootNode(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE); + NodeRef workingRootNodeRef = nodeService.createNode( + rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(NamespaceService.ALFRESCO_URI, "working root"), + ContentModel.TYPE_FOLDER).getChildRef(); + FileInfo file1 = fileFolderService.create(workingRootNodeRef, GUID.generate(), ContentModel.TYPE_CONTENT); + + // ensure at least 3 activities + JSONObject activityData = new JSONObject(); + activityData.put("title", GUID.generate()); + activityData.put("nodeRef", file1.getNodeRef()); + activityService.postActivity("org.alfresco.documentlibrary.file-added", null, "documentlibrary", activityData.toString(), userName1); + + AuthenticationUtil.popAuthentication(); + + return null; + } + }, false, true); + + // one of the users follows the other user (so that we can test that notification failures for one of the users does not + // affect other users) + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + @SuppressWarnings("synthetic-access") + public Void execute() throws Throwable + { + AuthenticationUtil.pushAuthentication(); + AuthenticationUtil.setFullyAuthenticatedUser(userName2); + + subscriptionService.follow(userName2, userName1); + + AuthenticationUtil.popAuthentication(); + + return null; + } + }, false, true); + + generateActivities(); + + // use our own user notifier for testing purposes + this.userNotifier = new ErrorProneUserNotifier(failingPersonNodeRef); + userNotifier.setNodeService(nodeService); + userNotifier.setNamespaceService(namespaceService); + userNotifier.setSiteService(siteService); + userNotifier.setActivityService(activityService); + userNotifier.setRepoAdminService(repoAdminService); + userNotifier.setActionService(actionService); + feedNotifier.setUserNotifier(userNotifier); + } + + private void generateActivities() throws Exception + { + // generate the activities + postLookup.execute(); + + Long maxSequence = postDAO.getMaxActivitySeq(); + while(maxSequence != null) + { + feedGenerator.execute(); + + maxSequence = postDAO.getMaxActivitySeq(); + } + } + + /** + * ALF-16155 test + */ + @Test + public void testFailedNotifications() + { + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); + + // execute the notifier, counting the number of successful and unsuccessful notifications + assertEquals(0, errorProneActionExecutor.getNumSuccess()); + assertEquals(0, errorProneActionExecutor.getNumFailed()); + feedNotifier.execute(1); + assertEquals(1, errorProneActionExecutor.getNumFailed()); // should be a single notification failure + assertTrue(errorProneActionExecutor.getNumSuccess() > 0); // others should have gone through ok + int numSuccessfulNotifications = errorProneActionExecutor.getNumSuccess(); + + // execute the notifier again, checking that there are no more notifications + feedNotifier.execute(1); + // should still be failing + assertEquals(2, errorProneActionExecutor.getNumFailed()); + // there should not be any more because they have been processed already + assertEquals(numSuccessfulNotifications, errorProneActionExecutor.getNumSuccess()); + } +} diff --git a/source/java/org/alfresco/repo/cache/TransactionalCache.java b/source/java/org/alfresco/repo/cache/TransactionalCache.java index 534ca57d57..a3761e55f8 100644 --- a/source/java/org/alfresco/repo/cache/TransactionalCache.java +++ b/source/java/org/alfresco/repo/cache/TransactionalCache.java @@ -19,6 +19,7 @@ package org.alfresco.repo.cache; import java.io.Serializable; +import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.LinkedHashMap; @@ -266,10 +267,52 @@ public class TransactionalCache * * @param noSharedCacheRead true to avoid reading from the shared cache for the transaction */ + @SuppressWarnings("unchecked") public void setDisableSharedCacheReadForTransaction(boolean noSharedCacheRead) { TransactionData txnData = getTransactionData(); - txnData.noSharedCacheRead = noSharedCacheRead; + + // If we are switching on noSharedCacheRead mode, convert all existing reads and updates to avoid 'consistent + // read' behaviour giving us a potentially out of date node already accessed + if (noSharedCacheRead && !txnData.noSharedCacheRead) + { + txnData.noSharedCacheRead = noSharedCacheRead; + String currentCacheRegion = TenantUtil.getCurrentDomain(); + for (Map.Entry> entry : new ArrayList>>( + txnData.updatedItemsCache.entrySet())) + { + Serializable cacheKey = entry.getKey(); + K key = null; + if (cacheKey instanceof CacheRegionKey) + { + CacheRegionKey cacheRegionKey = (CacheRegionKey) cacheKey; + if (currentCacheRegion.equals(cacheRegionKey.getCacheRegion())) + { + key = (K) cacheRegionKey.getCacheKey(); + } + } + else + { + key = (K) cacheKey; + } + + if (key != null) + { + CacheBucket bucket = entry.getValue(); + // Simply 'forget' reads + if (bucket instanceof ReadCacheBucket) + { + txnData.updatedItemsCache.remove(cacheKey); + } + // Convert updates to removes + else if (bucket instanceof UpdateCacheBucket) + { + remove(key); + } + // Leave new entries alone - they can't have come from the shared cache + } + } + } } /** diff --git a/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java b/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java index 98bdc6838b..2f8577d8cc 100644 --- a/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java @@ -4235,6 +4235,54 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO } } + @Override + public Set getCachedAncestors(List nodeIds) + { + // First, make sure 'level 1' nodes and their parents are in the cache + cacheNodesById(nodeIds); + for (Long nodeId : nodeIds) + { + // Filter out deleted nodes + if (exists(nodeId)) + { + getParentAssocsCached(nodeId); + } + } + // Now recurse on all ancestors in the cache + Set ancestors = new TreeSet(); + for (Long nodeId : nodeIds) + { + findCachedAncestors(nodeId, ancestors); + } + return ancestors; + } + + /** + * Uses the node and parent assocs cache content to recursively find the set of currently cached ancestor node IDs + */ + private void findCachedAncestors(Long nodeId, Set ancestors) + { + if (!ancestors.add(nodeId)) + { + return; // Already visited + } + Node node = nodesCache.getValue(nodeId); + if (node == null) + { + return; // Not in cache yet - will load in due course + } + Pair cacheKey = new Pair(nodeId, node.getTransaction().getChangeTxnId()); + ParentAssocsInfo value = parentAssocsCache.get(cacheKey); + if (value == null) + { + return; // Not in cache yet - will load in due course + } + for (ChildAssocEntity childAssoc : value.getParentAssocs().values()) + { + findCachedAncestors(childAssoc.getParentNode().getId(), ancestors); + } + } + @Override public void cacheNodesById(List nodeIds) { diff --git a/source/java/org/alfresco/repo/domain/permissions/AbstractAclCrudDAOImpl.java b/source/java/org/alfresco/repo/domain/permissions/AbstractAclCrudDAOImpl.java index 1ea9317d2b..cc612183eb 100644 --- a/source/java/org/alfresco/repo/domain/permissions/AbstractAclCrudDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/permissions/AbstractAclCrudDAOImpl.java @@ -26,6 +26,7 @@ import java.util.Map; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.repo.cache.SimpleCache; +import org.alfresco.repo.cache.TransactionalCache; import org.alfresco.repo.cache.lookup.EntityLookupCache; import org.alfresco.repo.cache.lookup.EntityLookupCache.EntityLookupCallbackDAO; import org.alfresco.repo.domain.CrcHelper; @@ -94,6 +95,12 @@ public abstract class AbstractAclCrudDAOImpl implements AclCrudDAO */ private EntityLookupCache aclEntityCache; + /** + * Backing transactional cache to allow read-through requests to be honoured + */ + private TransactionalCache aclEntityTransactionalCache; + + /** * Cache for the Authority entity:
* KEY: ID (Authority)
@@ -115,12 +122,13 @@ public abstract class AbstractAclCrudDAOImpl implements AclCrudDAO * * @param aclEntityCache the cache of IDs to AclEntities */ - public void setAclEntityCache(SimpleCache aclEntityCache) + public void setAclEntityCache(TransactionalCache aclEntityCache) { this.aclEntityCache = new EntityLookupCache( aclEntityCache, CACHE_REGION_ACL, aclEntityDaoCallback); + this.aclEntityTransactionalCache = aclEntityCache; } /** @@ -200,6 +208,12 @@ public abstract class AbstractAclCrudDAOImpl implements AclCrudDAO return entityPair.getSecond(); } + @Override + public void setCheckAclConsistency() + { + aclEntityTransactionalCache.setDisableSharedCacheReadForTransaction(true); + } + public AclUpdateEntity getAclForUpdate(long id) { AclEntity acl = getAclImpl(id); diff --git a/source/java/org/alfresco/repo/domain/permissions/AclCrudDAO.java b/source/java/org/alfresco/repo/domain/permissions/AclCrudDAO.java index b68a6ab7bc..bc61c9bb8b 100644 --- a/source/java/org/alfresco/repo/domain/permissions/AclCrudDAO.java +++ b/source/java/org/alfresco/repo/domain/permissions/AclCrudDAO.java @@ -45,6 +45,12 @@ import org.alfresco.util.Pair; */ public interface AclCrudDAO { + /** + * Transaction-scope setting to make the DAO guarantee the validity of all caches: some cache data will be reloaded; + * some cache data will be considered safe. + */ + public void setCheckAclConsistency(); + // // Access Control List (ACL) // diff --git a/source/java/org/alfresco/repo/domain/permissions/AclDAO.java b/source/java/org/alfresco/repo/domain/permissions/AclDAO.java index b455c7eafc..ce8b5453ad 100644 --- a/source/java/org/alfresco/repo/domain/permissions/AclDAO.java +++ b/source/java/org/alfresco/repo/domain/permissions/AclDAO.java @@ -34,6 +34,12 @@ import org.alfresco.repo.security.permissions.impl.AclChange; */ public interface AclDAO { + /** + * Transaction-scope setting to make the DAO guarantee the validity of all caches: some cache data will be reloaded; + * some cache data will be considered safe. + */ + public void setCheckAclConsistency(); + /** * Get an ACL (including entries) */ diff --git a/source/java/org/alfresco/repo/domain/permissions/AclDAOImpl.java b/source/java/org/alfresco/repo/domain/permissions/AclDAOImpl.java index f0127c2cdb..40b9acae39 100644 --- a/source/java/org/alfresco/repo/domain/permissions/AclDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/permissions/AclDAOImpl.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 * @@ -21,6 +21,8 @@ package org.alfresco.repo.domain.permissions; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; @@ -70,11 +72,8 @@ public class AclDAOImpl implements AclDAO private AclCrudDAO aclCrudDAO; private NodeDAO nodeDAO; private TenantService tenantService; - private SimpleCache aclCache; - private SimpleCache> readersCache; + private SimpleCache aclCache; - private SimpleCache> readersDeniedCache; - private enum WriteMode { /** @@ -132,27 +131,11 @@ public class AclDAOImpl implements AclDAO * * @param aclCache */ - public void setAclCache(SimpleCache aclCache) + public void setAclCache(SimpleCache aclCache) { this.aclCache = aclCache; } - /** - * @param readersCache the readersCache to set - */ - public void setReadersCache(SimpleCache> readersCache) - { - this.readersCache = readersCache; - } - - /** - * @param readersDeniedCache the readersDeniedCache to set - */ - public void setReadersDeniedCache(SimpleCache> readersDeniedCache) - { - this.readersDeniedCache = readersDeniedCache; - } - /** * {@inheritDoc} */ @@ -348,8 +331,8 @@ public class AclDAOImpl implements AclDAO } getWritable(created, toInherit, excluded, toAdd, toInherit, false, changes, WriteMode.CREATE_AND_INHERIT); - - return createdAcl; + // Fetch an up-to-date version + return getAcl(created); } private void getWritable( @@ -442,9 +425,6 @@ public class AclDAOImpl implements AclDAO AclUpdateEntity acl = aclCrudDAO.getAclForUpdate(id); if (!acl.isLatest()) { - aclCache.remove(id); - readersCache.remove(id); - readersDeniedCache.remove(id); return new AclChangeImpl(id, id, acl.getAclType(), acl.getAclType()); } @@ -493,9 +473,6 @@ public class AclDAOImpl implements AclDAO } acl.setAclChangeSetId(getCurrentChangeSetId()); aclCrudDAO.updateAcl(acl); - aclCache.remove(id); - readersCache.remove(id); - readersDeniedCache.remove(id); return new AclChangeImpl(id, id, acl.getAclType(), acl.getAclType()); } else if ((acl.getAclChangeSetId() == getCurrentChangeSetId()) && (!requiresVersion) && (!acl.getRequiresVersion())) @@ -533,9 +510,6 @@ public class AclDAOImpl implements AclDAO acl.setInheritsFrom(inheritsFrom); } aclCrudDAO.updateAcl(acl); - aclCache.remove(id); - readersCache.remove(id); - readersDeniedCache.remove(id); return new AclChangeImpl(id, id, acl.getAclType(), acl.getAclType()); } else @@ -623,9 +597,6 @@ public class AclDAOImpl implements AclDAO acl.setLatest(Boolean.FALSE); acl.setRequiresVersion(Boolean.FALSE); aclCrudDAO.updateAcl(acl); - aclCache.remove(id); - readersCache.remove(id); - readersDeniedCache.remove(id); return new AclChangeImpl(id, created, acl.getAclType(), newAcl.getAclType()); } } @@ -751,13 +722,13 @@ public class AclDAOImpl implements AclDAO @Override public List deleteAccessControlEntries(final String authority) { - List acls = new ArrayList(); + List aclChanges = new LinkedList(); // get authority Authority authEntity = aclCrudDAO.getAuthority(authority); if (authEntity == null) { - return acls; + return aclChanges; } List aces = new ArrayList(); @@ -767,6 +738,7 @@ public class AclDAOImpl implements AclDAO boolean leaveAuthority = false; if (members.size() > 0) { + Set acls = new HashSet(members.size() * 2); List membersToDelete = new ArrayList(members.size()); // fix up members and extract acls and aces @@ -817,12 +789,9 @@ public class AclDAOImpl implements AclDAO if (!hasAnotherTenantNodes) { - aclCache.remove(aclId); - readersCache.remove(aclId); - readersDeniedCache.remove(aclId); - - Acl list = aclCrudDAO.getAcl(aclId); - acls.add(new AclChangeImpl(aclId, aclId, list.getAclType(), list.getAclType())); + AclUpdateEntity list = aclCrudDAO.getAclForUpdate(aclId); + aclChanges.add(new AclChangeImpl(aclId, aclId, list.getAclType(), list.getAclType())); + acls.add(list); membersToDelete.add(aclMemberId); aces.add((Long)aceId); } @@ -830,6 +799,13 @@ public class AclDAOImpl implements AclDAO // delete list of acl members aclCrudDAO.deleteAclMembers(membersToDelete); + + // Remember to 'touch' all affected ACLs + for (AclUpdateEntity acl : acls) + { + acl.setAclChangeSetId(getCurrentChangeSetId()); + aclCrudDAO.updateAcl(acl); + } } if (!leaveAuthority) @@ -859,7 +835,7 @@ public class AclDAOImpl implements AclDAO } } - return acls; + return aclChanges; } /** @@ -874,10 +850,6 @@ public class AclDAOImpl implements AclDAO // delete acl members & acl aclCrudDAO.deleteAclMembersByAcl(aclId); aclCrudDAO.deleteAcl(aclId); - - aclCache.remove(aclId); - readersCache.remove(aclId); - readersDeniedCache.remove(aclId); } if (dbAcl.getAclType() == ACLType.SHARED) { @@ -893,10 +865,6 @@ public class AclDAOImpl implements AclDAO // delete acl members & acl aclCrudDAO.deleteAclMembersByAcl(aclId); aclCrudDAO.deleteAcl(aclId); - - aclCache.remove(aclId); - readersCache.remove(aclId); - readersDeniedCache.remove(aclId); } } else @@ -1008,10 +976,6 @@ public class AclDAOImpl implements AclDAO aclCrudDAO.deleteAcl(acl.getId()); } - // remove the deleted acl from the cache - aclCache.remove(id); - readersCache.remove(id); - readersDeniedCache.remove(id); acls.add(new AclChangeImpl(id, null, acl.getAclType(), null)); return acls; } @@ -1075,37 +1039,31 @@ public class AclDAOImpl implements AclDAO return aclCrudDAO.getAcl(id); } + @Override + public void setCheckAclConsistency() + { + aclCrudDAO.setCheckAclConsistency(); + } + /** * {@inheritDoc} */ @Override public AccessControlList getAccessControlList(Long id) { - AccessControlList acl = aclCache.get(id); - if (acl == null) - { - acl = getAccessControlListImpl(id); - aclCache.put(id, acl); - } - else - { - // System.out.println("Used cache for "+id); - } - return acl; - } - - /** - * @return the access control list - */ - private AccessControlList getAccessControlListImpl(final Long id) - { - SimpleAccessControlList acl = new SimpleAccessControlList(); + // Used the cached properties as our cache key AccessControlListProperties properties = getAccessControlListProperties(id); if (properties == null) { return null; } + AccessControlList aclCached = aclCache.get((Serializable)properties); + if (aclCached != null) + { + return aclCached; + } + SimpleAccessControlList acl = new SimpleAccessControlList(); acl.setProperties(properties); List> results = aclCrudDAO.getAcesAndAuthoritiesByAcl(id); @@ -1143,6 +1101,9 @@ public class AclDAOImpl implements AclDAO Collections.sort(entries); acl.setEntries(entries); + + // Cache it for next time + aclCache.put((Serializable)properties, acl); return acl; } @@ -1153,7 +1114,6 @@ public class AclDAOImpl implements AclDAO @Override public Long getInheritedAccessControlList(Long id) { - aclCache.remove(id); AclUpdateEntity acl = aclCrudDAO.getAclForUpdate(id); if (acl.getAclType() == ACLType.OLD) { @@ -1355,9 +1315,6 @@ public class AclDAOImpl implements AclDAO acl.setInherits(Boolean.TRUE); acl.setAclChangeSetId(getCurrentChangeSetId()); aclCrudDAO.updateAcl(acl); - aclCache.remove(id); - readersCache.remove(id); - readersDeniedCache.remove(id); changes.add(new AclChangeImpl(id, id, acl.getAclType(), acl.getAclType())); return changes; case SHARED: @@ -1394,7 +1351,6 @@ public class AclDAOImpl implements AclDAO @Override public List disableInheritance(Long id, boolean setInheritedOnAcl) { - aclCache.remove(id); AclUpdateEntity acl = aclCrudDAO.getAclForUpdate(id); List changes = new ArrayList(1); switch (acl.getAclType()) @@ -1406,9 +1362,6 @@ public class AclDAOImpl implements AclDAO acl.setInherits(Boolean.FALSE); acl.setAclChangeSetId(getCurrentChangeSetId()); aclCrudDAO.updateAcl(acl); - aclCache.remove(id); - readersCache.remove(id); - readersDeniedCache.remove(id); changes.add(new AclChangeImpl(id, id, acl.getAclType(), acl.getAclType())); return changes; case SHARED: @@ -1442,9 +1395,6 @@ public class AclDAOImpl implements AclDAO aclToCopy.setRequiresVersion(true); aclToCopy.setAclChangeSetId(getCurrentChangeSetId()); aclCrudDAO.updateAcl(aclToCopy); - aclCache.remove(toCopy); - readersCache.remove(toCopy); - readersDeniedCache.remove(toCopy); inheritedId = getInheritedAccessControlList(toCopy); if ((inheritedId != null) && (!inheritedId.equals(toCopy))) { @@ -1452,9 +1402,6 @@ public class AclDAOImpl implements AclDAO inheritedAcl.setRequiresVersion(true); inheritedAcl.setAclChangeSetId(getCurrentChangeSetId()); aclCrudDAO.updateAcl(inheritedAcl); - aclCache.remove(inheritedId); - readersCache.remove(inheritedId); - readersDeniedCache.remove(inheritedId); } return toCopy; case REDIRECT: diff --git a/source/java/org/alfresco/repo/domain/permissions/AclEntity.java b/source/java/org/alfresco/repo/domain/permissions/AclEntity.java index 437851c1ce..b947e13a15 100644 --- a/source/java/org/alfresco/repo/domain/permissions/AclEntity.java +++ b/source/java/org/alfresco/repo/domain/permissions/AclEntity.java @@ -199,9 +199,13 @@ public class AclEntity implements Acl, Serializable @Override public int hashCode() { - return (id == null ? 0 : id.hashCode()); + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + result = prime * result + ((version == null) ? 0 : version.hashCode()); + return result; } - + @Override public boolean equals(Object obj) { @@ -211,8 +215,9 @@ public class AclEntity implements Acl, Serializable } else if (obj instanceof AclEntity) { - AclEntity that = (AclEntity)obj; - return (EqualsHelper.nullSafeEquals(this.id, that.id)); + AclEntity that = (AclEntity) obj; + return EqualsHelper.nullSafeEquals(this.id, that.id) + && EqualsHelper.nullSafeEquals(this.version, that.version); } else { diff --git a/source/java/org/alfresco/repo/forms/processor/node/ContentModelFormProcessor.java b/source/java/org/alfresco/repo/forms/processor/node/ContentModelFormProcessor.java index c10d4f2e05..da1485cd75 100644 --- a/source/java/org/alfresco/repo/forms/processor/node/ContentModelFormProcessor.java +++ b/source/java/org/alfresco/repo/forms/processor/node/ContentModelFormProcessor.java @@ -48,6 +48,7 @@ import org.alfresco.repo.forms.processor.FilteredFormProcessor; import org.alfresco.repo.forms.processor.FormCreationData; import org.alfresco.service.cmr.dictionary.AssociationDefinition; import org.alfresco.service.cmr.dictionary.ChildAssociationDefinition; +import org.alfresco.service.cmr.dictionary.ClassDefinition; import org.alfresco.service.cmr.dictionary.ConstraintDefinition; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.DictionaryService; @@ -502,21 +503,36 @@ public abstract class ContentModelFormProcessor extends // ensure that the association being persisted is defined in the model AssociationDefinition assocDef = assocDefs.get(fullQName); - // TODO: if the association is not defined on the node, check for the association - // in all models, however, the source of an association can be critical so we - // can't just look up the association in the model regardless. We need to - // either check the source class of the node and the assoc def match or we - // check that the association was defined as part of an aspect (where by it's - // nature can have any source type) - + // If the association is not defined on this node i.e. it is not defined for this node's content + // type and is not defined for any of the aspects that have already been applied to this node, + // then assocDef here will be null. This is because assocDef is passed in to this method, its value + // having been obtained from dictionaryService.getAnonymousType() + // + // However if the association type is defined on an aspect which has not yet been applied to this node + // then setting a value for this association should lead to the automatic application of the aspect defining it. if (assocDef == null) { - if (getLogger().isWarnEnabled()) - { - getLogger().warn("Ignoring field '" + fieldName + "' as an association definition can not be found"); - } + // Is it defined on any other type? + AssociationDefinition assocDefFromDictionary = this.dictionaryService.getAssociation(fullQName); - return; + // If the association is defined on any *aspect* type... + if (assocDefFromDictionary != null && assocDefFromDictionary.getSourceClass().isAspect()) + { + // ... then it should be safe to proceed with applying the association value. + assocDef = assocDefFromDictionary; + } + else + { + // ... else it is either undefined in the dictionary service or is defined on a concrete + // content type which is inconsistent with the node's type. We'll ignore it, which is what + // has been done with unhandled associations up to this point. + if (getLogger().isWarnEnabled()) + { + getLogger().warn("Ignoring field '" + fieldName + "' as a valid association definition can not be found"); + } + + return; + } } String value = (String) fieldData.getValue(); diff --git a/source/java/org/alfresco/repo/imap/AlfrescoImapFolder.java b/source/java/org/alfresco/repo/imap/AlfrescoImapFolder.java index dd1d3bd28a..1aa8f6b162 100644 --- a/source/java/org/alfresco/repo/imap/AlfrescoImapFolder.java +++ b/source/java/org/alfresco/repo/imap/AlfrescoImapFolder.java @@ -344,7 +344,9 @@ public class AlfrescoImapFolder extends AbstractImapFolder implements Serializab } else { - serviceRegistry.getFileFolderService().copy(sourceMessageFileInfo.getNodeRef(), destFolderNodeRef, null); + String fileName = (String) serviceRegistry.getNodeService().getProperty(sourceMessageFileInfo.getNodeRef(), ContentModel.PROP_NAME); + String newFileName = imapService.generateUniqueFilename(destFolderNodeRef, fileName); + serviceRegistry.getFileFolderService().copy(sourceMessageFileInfo.getNodeRef(), destFolderNodeRef, newFileName); } } diff --git a/source/java/org/alfresco/repo/imap/AttachmentsExtractor.java b/source/java/org/alfresco/repo/imap/AttachmentsExtractor.java index 9c4b75b59c..3deb1d0521 100644 --- a/source/java/org/alfresco/repo/imap/AttachmentsExtractor.java +++ b/source/java/org/alfresco/repo/imap/AttachmentsExtractor.java @@ -71,6 +71,7 @@ public class AttachmentsExtractor private FileFolderService fileFolderService; private NodeService nodeService; + private ImapService imapService; private ServiceRegistry serviceRegistry; private RepositoryFolderConfigBean attachmentsFolder; private NodeRef attachmentsFolderRef; @@ -84,6 +85,11 @@ public class AttachmentsExtractor public void setNodeService(NodeService nodeService) { this.nodeService = nodeService; + } + + public void setImapService(ImapService imapService) + { + this.imapService = imapService; } public void setAttachmentsFolder(RepositoryFolderConfigBean attachmentsFolder) @@ -193,23 +199,11 @@ public class AttachmentsExtractor } else { - String name = fileName; - String ext = ""; - if (fileName.lastIndexOf(".") != -1) - { - int index = fileName.lastIndexOf("."); - name = fileName.substring(0, index); - ext = fileName.substring(index); - - } - - int copyNum = 0; - do - { - copyNum++; - } while (fileFolderService.searchSimple(attachmentsFolderRef, name + " (" + copyNum + ")" + ext) != null); - - FileInfo createdFile = fileFolderService.create(attachmentsFolderRef, name + " (" + copyNum + ")" + ext, ContentModel.TYPE_CONTENT); + + + String newFileName = imapService.generateUniqueFilename(attachmentsFolderRef, fileName); + + FileInfo createdFile = fileFolderService.create(attachmentsFolderRef, newFileName, ContentModel.TYPE_CONTENT); nodeService.createAssociation(messageFile, createdFile.getNodeRef(), ImapModel.ASSOC_IMAP_ATTACHMENT); attachmentFile = createdFile.getNodeRef(); diff --git a/source/java/org/alfresco/repo/imap/ImapService.java b/source/java/org/alfresco/repo/imap/ImapService.java index 30ebddc27a..1296e4a85d 100644 --- a/source/java/org/alfresco/repo/imap/ImapService.java +++ b/source/java/org/alfresco/repo/imap/ImapService.java @@ -315,6 +315,8 @@ public interface ImapService */ public void extractAttachments(NodeRef messageRef, MimeMessage originalMessage) throws IOException, MessagingException; + public String generateUniqueFilename(NodeRef destFolderNodeRef, String fileName); + static class FolderStatus { public final int messageCount; diff --git a/source/java/org/alfresco/repo/imap/ImapServiceImpl.java b/source/java/org/alfresco/repo/imap/ImapServiceImpl.java index e19368dd91..65afa7cdfc 100644 --- a/source/java/org/alfresco/repo/imap/ImapServiceImpl.java +++ b/source/java/org/alfresco/repo/imap/ImapServiceImpl.java @@ -2107,6 +2107,29 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol attachmentsExtractor.extractAttachments(messageRef, originalMessage); } + public String generateUniqueFilename(NodeRef destFolderNodeRef, String fileName) + { + if(fileFolderService.searchSimple(destFolderNodeRef, fileName) != null) + { + String name = fileName; + String ext = ""; + if (fileName.lastIndexOf(".") != -1) + { + int index = fileName.lastIndexOf("."); + name = fileName.substring(0, index); + ext = fileName.substring(index); + } + int copyNum = 0; + do + { + copyNum++; + } while (fileFolderService.searchSimple(destFolderNodeRef, name + " (" + copyNum + ")" + ext) != null); + fileName = name + " (" + copyNum + ")" + ext; + } + + return fileName; + } + static class CacheItem { private Date modified; diff --git a/source/java/org/alfresco/repo/model/ml/MultilingualContentServiceImpl.java b/source/java/org/alfresco/repo/model/ml/MultilingualContentServiceImpl.java index dd66696e19..14c2c936e1 100644 --- a/source/java/org/alfresco/repo/model/ml/MultilingualContentServiceImpl.java +++ b/source/java/org/alfresco/repo/model/ml/MultilingualContentServiceImpl.java @@ -442,22 +442,44 @@ public class MultilingualContentServiceImpl implements MultilingualContentServic * @param translationNodeRef a translation */ private void unmakeTranslationSimple(NodeRef translationNodeRef) - { - if (nodeService.hasAspect(translationNodeRef, ContentModel.ASPECT_MULTILINGUAL_EMPTY_TRANSLATION)) + { + try { - nodeService.removeAspect(translationNodeRef, ContentModel.ASPECT_MULTILINGUAL_EMPTY_TRANSLATION); - nodeService.addAspect(translationNodeRef, ContentModel.ASPECT_TEMPORARY, null); + this.policyBehaviourFilter.disableBehaviour(ContentModel.TYPE_MULTILINGUAL_CONTAINER); + if (nodeService.hasAspect(translationNodeRef, ContentModel.ASPECT_MULTILINGUAL_EMPTY_TRANSLATION)) + { + nodeService.removeAspect(translationNodeRef, ContentModel.ASPECT_MULTILINGUAL_EMPTY_TRANSLATION); + nodeService.addAspect(translationNodeRef, ContentModel.ASPECT_TEMPORARY, null); + } + else + { + if (nodeService.hasAspect(translationNodeRef, ContentModel.ASPECT_MULTILINGUAL_DOCUMENT)) + { + nodeService.removeAspect(translationNodeRef, ContentModel.ASPECT_MULTILINGUAL_DOCUMENT); + } + } + List assocRefs = nodeService.getParentAssocs( + translationNodeRef, + ContentModel.ASSOC_MULTILINGUAL_CHILD, + RegexQNamePattern.MATCH_ALL); + if (assocRefs.size() != 1) + { + throw new AlfrescoRuntimeException( + "Unable to remove ASSOC_MULTILINGUAL_CHILD on : " + translationNodeRef.toString()); + } } - else + finally { - nodeService.removeAspect(translationNodeRef, ContentModel.ASPECT_MULTILINGUAL_DOCUMENT); + this.policyBehaviourFilter.enableBehaviour(ContentModel.TYPE_MULTILINGUAL_CONTAINER); } } /** @inheritDoc */ public void unmakeTranslation(NodeRef translationNodeRef) { - if (isPivotTranslation(translationNodeRef)) + if ((nodeService.hasAspect(translationNodeRef, ContentModel.ASPECT_MULTILINGUAL_EMPTY_TRANSLATION) || + nodeService.hasAspect(translationNodeRef, ContentModel.ASPECT_MULTILINGUAL_DOCUMENT)) && + isPivotTranslation(translationNodeRef)) { NodeRef containerNodeRef = getMLContainer(translationNodeRef, true); // We have not cleaned up all other translations diff --git a/source/java/org/alfresco/repo/model/ml/tools/MultilingualContentServiceImplTest.java b/source/java/org/alfresco/repo/model/ml/tools/MultilingualContentServiceImplTest.java index b70b5ef3c4..6c89d8804d 100644 --- a/source/java/org/alfresco/repo/model/ml/tools/MultilingualContentServiceImplTest.java +++ b/source/java/org/alfresco/repo/model/ml/tools/MultilingualContentServiceImplTest.java @@ -164,6 +164,59 @@ public class MultilingualContentServiceImplTest extends AbstractMultilingualTest assertEquals("Pivot node ref not correct", frenchContentNodeRef, multilingualContentService.getPivotTranslation(mlContainerNodeRef)); } + /** + * Testing unmakeTranslation + * @throws Exception + */ + public void testUnmakeTranslation() throws Exception + { + NodeRef frenchContentNodeRef = createContent(); + multilingualContentService.makeTranslation(frenchContentNodeRef, Locale.FRENCH); + NodeRef mlContainerNodeRef = multilingualContentService.getTranslationContainer(frenchContentNodeRef); + + NodeRef englishContentNodeRef = createContent(); + multilingualContentService.addTranslation(englishContentNodeRef, frenchContentNodeRef, Locale.ENGLISH); + + NodeRef germanContentNodeRef = createContent(); + multilingualContentService.addTranslation(germanContentNodeRef, frenchContentNodeRef, Locale.GERMAN); + + multilingualContentService.unmakeTranslation(englishContentNodeRef); + multilingualContentService.unmakeTranslation(germanContentNodeRef); + //multilingualContentService.unmakeTranslation(frenchContentNodeRef); + + //calling unmakeTranslation() dissolves the multiligual document. + //none of the documents composing the multilingual document + assertTrue("The document should not be multilingual!", + !multilingualContentService.isTranslation(englishContentNodeRef) && + !multilingualContentService.isTranslation(germanContentNodeRef)); + } + + /** + * Testing unmakeTranslation() on pivot + * @throws Exception + */ + public void testUnmakeTranslationOnPivot() throws Exception + { + NodeRef frenchContentNodeRef = createContent(); + multilingualContentService.makeTranslation(frenchContentNodeRef, Locale.FRENCH); + NodeRef mlContainerNodeRef = multilingualContentService.getTranslationContainer(frenchContentNodeRef); + + NodeRef englishContentNodeRef = createContent(); + multilingualContentService.addTranslation(englishContentNodeRef, frenchContentNodeRef, Locale.ENGLISH); + + NodeRef germanContentNodeRef = createContent(); + multilingualContentService.addTranslation(germanContentNodeRef, frenchContentNodeRef, Locale.GERMAN); + + multilingualContentService.unmakeTranslation(frenchContentNodeRef); + + //calling unmakeTranslation() dissolves the multiligual document. + //none of the documents composing the multilingual document + assertTrue("The document should not be multilingual!", + !multilingualContentService.isTranslation(frenchContentNodeRef) && + !multilingualContentService.isTranslation(englishContentNodeRef) && + !multilingualContentService.isTranslation(germanContentNodeRef)); + } + @SuppressWarnings("unused") public void testCreateEmptyTranslation() throws Exception { diff --git a/source/java/org/alfresco/repo/node/NodeBulkLoader.java b/source/java/org/alfresco/repo/node/NodeBulkLoader.java index a63a0f1272..449bb3d398 100644 --- a/source/java/org/alfresco/repo/node/NodeBulkLoader.java +++ b/source/java/org/alfresco/repo/node/NodeBulkLoader.java @@ -19,6 +19,7 @@ package org.alfresco.repo.node; import java.util.List; +import java.util.Set; import org.alfresco.service.cmr.repository.NodeRef; @@ -32,6 +33,15 @@ import org.alfresco.service.cmr.repository.NodeRef; */ public interface NodeBulkLoader { + /** + * Gets the current set of cached ancestors of the given list of nodes. + * + * @param nodeIds + * a list of node IDs to visit + * @return the current set of cached ancestors of the given list of nodes, including the nodes themselves. + */ + public Set getCachedAncestors(List nodeIds); + /** * Transaction-scope setting to make the Node loader to guarantee the validity of all * caches: some cache data will be reloaded; some cache data will be considered safe. diff --git a/source/java/org/alfresco/repo/rule/RuleServiceCoverageTest.java b/source/java/org/alfresco/repo/rule/RuleServiceCoverageTest.java index 3461ab368e..df91241144 100644 --- a/source/java/org/alfresco/repo/rule/RuleServiceCoverageTest.java +++ b/source/java/org/alfresco/repo/rule/RuleServiceCoverageTest.java @@ -929,14 +929,13 @@ public class RuleServiceCoverageTest extends TestCase .getBean("OutboundSMTP")).getApplicationContext().getBean("mail"); mailService.setTestMode(true); mailService.clearLastTestMessage(); - - NodeRef contentNodeRef = this.nodeService.createNode( + + this.nodeService.createNode( this.nodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName(TEST_NAMESPACE, "children"), ContentModel.TYPE_CONTENT, getContentProperties()).getChildRef(); - addContentToNode(contentNodeRef); // An email should appear in the recipients email // System.out.println(NodeStoreInspector.dumpNodeStore(this.nodeService, this.testStoreRef)); @@ -1664,9 +1663,53 @@ public class RuleServiceCoverageTest extends TestCase ContentModel.ASSOC_CHILDREN, QName.createQName(TEST_NAMESPACE, "children"), ContentModel.TYPE_CONTENT).getChildRef(); + assertTrue(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); + addContentToNode(contentNodeRef); + assertTrue(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); + + // ALF-14744 / MNT-187: Create a content node - this time with 'empty content' in the same transaction + contentNodeRef = this.transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public NodeRef execute() throws Throwable + { + NodeRef contentNodeRef = RuleServiceCoverageTest.this.nodeService.createNode( + RuleServiceCoverageTest.this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "children"), + ContentModel.TYPE_CONTENT).getChildRef(); + ContentWriter contentWriter = RuleServiceCoverageTest.this.contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, true); + assertNotNull(contentWriter); + contentWriter.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN); + contentWriter.setEncoding("UTF-8"); + contentWriter.putContent(""); + return contentNodeRef; + } + }); assertFalse(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); addContentToNode(contentNodeRef); assertTrue(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); + + // ALF-14744 / MNT-187: Create a content node - this time with the 'no content' aspect in the same transaction + contentNodeRef = this.transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public NodeRef execute() throws Throwable + { + NodeRef contentNodeRef = RuleServiceCoverageTest.this.nodeService.createNode( + RuleServiceCoverageTest.this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "children"), + ContentModel.TYPE_CONTENT).getChildRef(); + RuleServiceCoverageTest.this.nodeService.addAspect(contentNodeRef, ContentModel.ASPECT_NO_CONTENT, null); + return contentNodeRef; + } + }); + assertFalse(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); + addContentToNode(contentNodeRef); + assertFalse(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); + nodeService.removeAspect(contentNodeRef, ContentModel.ASPECT_NO_CONTENT); + assertTrue(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); // Create a node to be moved NodeRef moveNode = this.nodeService.createNode( diff --git a/source/java/org/alfresco/repo/rule/RuleTypeImplTest.java b/source/java/org/alfresco/repo/rule/RuleTypeImplTest.java index cd530ae9d8..4357f3c59d 100644 --- a/source/java/org/alfresco/repo/rule/RuleTypeImplTest.java +++ b/source/java/org/alfresco/repo/rule/RuleTypeImplTest.java @@ -83,31 +83,32 @@ public class RuleTypeImplTest extends BaseSpringTest public void testMockInboundRuleType() { - NodeRef nodeRef = this.nodeService.createNode( - this.rootNodeRef, - ContentModel.ASSOC_CHILDREN, - ContentModel.ASSOC_CHILDREN, - ContentModel.TYPE_CONTENT).getChildRef(); - NodeRef nodeRef2 = this.nodeService.createNode( - this.rootNodeRef, - ContentModel.ASSOC_CHILDREN, - ContentModel.ASSOC_CHILDREN, - ContentModel.TYPE_CONTAINER).getChildRef(); - List triggers = new ArrayList(2); triggers.add((RuleTrigger)this.applicationContext.getBean("on-content-create-trigger")); - triggers.add((RuleTrigger)this.applicationContext.getBean("on-content-update-trigger")); + triggers.add((RuleTrigger)this.applicationContext.getBean("on-create-node-trigger")); triggers.add((RuleTrigger)this.applicationContext.getBean("on-create-child-association-trigger")); ExtendedRuleType ruleType = new ExtendedRuleType(triggers); assertFalse(ruleType.rulesTriggered); + NodeRef nodeRef = this.nodeService.createNode( + this.rootNodeRef, + ContentModel.ASSOC_CHILDREN, + ContentModel.ASSOC_CHILDREN, + ContentModel.TYPE_CONTENT).getChildRef(); + // Update some content in order to trigger the rule type ContentWriter contentWriter = this.contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true); contentWriter.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN); contentWriter.putContent("any old content"); assertTrue(ruleType.rulesTriggered); + NodeRef nodeRef2 = this.nodeService.createNode( + this.rootNodeRef, + ContentModel.ASSOC_CHILDREN, + ContentModel.ASSOC_CHILDREN, + ContentModel.TYPE_CONTAINER).getChildRef(); + // Reset ruleType.rulesTriggered = false; assertFalse(ruleType.rulesTriggered); diff --git a/source/java/org/alfresco/repo/rule/ruletrigger/CreateNodeRuleTrigger.java b/source/java/org/alfresco/repo/rule/ruletrigger/CreateNodeRuleTrigger.java index 4c8bdf4fc8..6bf75532dc 100644 --- a/source/java/org/alfresco/repo/rule/ruletrigger/CreateNodeRuleTrigger.java +++ b/source/java/org/alfresco/repo/rule/ruletrigger/CreateNodeRuleTrigger.java @@ -26,11 +26,6 @@ import org.alfresco.repo.policy.Behaviour.NotificationFrequency; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.rule.RuntimeRuleService; import org.alfresco.repo.transaction.TransactionalResourceHelper; -import org.alfresco.service.cmr.dictionary.AspectDefinition; -import org.alfresco.service.cmr.dictionary.ClassDefinition; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.service.cmr.dictionary.PropertyDefinition; -import org.alfresco.service.cmr.dictionary.TypeDefinition; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.namespace.NamespaceService; @@ -112,26 +107,6 @@ public class CreateNodeRuleTrigger extends RuleTriggerAbstractBase new JavaBehaviour(this, "onRemoveAspect", NotificationFrequency.EVERY_EVENT)); } - - /** - * Return true if provided classDef has property that has propertyType type - */ - private boolean hasPropertyOfType(ClassDefinition classDef, QName propertyType) - { - if (classDef != null) - { - for (PropertyDefinition propertyDef : classDef.getProperties().values()) - { - if (propertyDef.getDataType().getName().equals(propertyType) && !propertyDef.isMultiValued()) - { - return true; - } - } - } - - return false; - } - /** * {@inheritDoc} */ @@ -148,27 +123,6 @@ public class CreateNodeRuleTrigger extends RuleTriggerAbstractBase Set newNodeRefSet = TransactionalResourceHelper.getSet(RULE_TRIGGER_NEW_NODES); newNodeRefSet.add(nodeRef); - // If the node's type or aspects have a single-valued content property, don't fire the trigger, as it would be - // handled by on-content-create-trigger, according to its settings for empty content - - // Check node type's properties - QName nodeType = nodeService.getType(nodeRef); - TypeDefinition typeDefinition = dictionaryService.getType(nodeType); - if (hasPropertyOfType(typeDefinition, DataTypeDefinition.CONTENT)) - { - return; - } - - // Check node aspects' properties - for (QName aspectQName : nodeService.getAspects(nodeRef)) - { - AspectDefinition aspectDefinition = dictionaryService.getAspect(aspectQName); - if (hasPropertyOfType(aspectDefinition, DataTypeDefinition.CONTENT)) - { - return; - } - } - if (nodeRef != null && nodeService.exists(nodeRef) == true && nodeService.hasAspect(nodeRef, ContentModel.ASPECT_NO_CONTENT) == false) diff --git a/source/java/org/alfresco/repo/rule/ruletrigger/OnContentUpdateRuleTrigger.java b/source/java/org/alfresco/repo/rule/ruletrigger/OnContentUpdateRuleTrigger.java deleted file mode 100644 index 371a393a38..0000000000 --- a/source/java/org/alfresco/repo/rule/ruletrigger/OnContentUpdateRuleTrigger.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * 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.rule.ruletrigger; - -import java.util.List; -import java.util.Set; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.content.ContentServicePolicies; -import org.alfresco.repo.policy.JavaBehaviour; -import org.alfresco.repo.transaction.TransactionalResourceHelper; -import org.alfresco.service.cmr.repository.ChildAssociationRef; -import org.alfresco.service.cmr.repository.ContentData; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.namespace.QName; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * @author Roy Wetherall - */ -public class OnContentUpdateRuleTrigger extends RuleTriggerAbstractBase - implements ContentServicePolicies.OnContentPropertyUpdatePolicy -{ - /** - * The logger - */ - private static Log logger = LogFactory.getLog(OnContentUpdateRuleTrigger.class); - - /** True trigger on new content, false otherwise */ - private boolean onNewContent = false; - - /** True trigger parent rules, false otherwier */ - private boolean triggerParentRules = true; - - /** - * If set to true the trigger will fire on new content, otherwise it will fire on content update - * - * @param onNewContent indicates whether to fire on content create or update - */ - public void setOnNewContent(boolean onNewContent) - { - this.onNewContent = onNewContent; - } - - /** - * Indicates whether the parent rules should be triggered or the rules on the node itself - * - * @param triggerParentRules true trigger parent rules, false otherwise - */ - public void setTriggerParentRules(boolean triggerParentRules) - { - this.triggerParentRules = triggerParentRules; - } - - /* - * @see org.alfresco.repo.rule.ruletrigger.RuleTrigger#registerRuleTrigger() - */ - public void registerRuleTrigger() - { - // Bind behaviour - this.policyComponent.bindClassBehaviour( - ContentServicePolicies.OnContentPropertyUpdatePolicy.QNAME, - this, - new JavaBehaviour(this, "onContentPropertyUpdate")); - } - - - - /** - * @see org.alfresco.repo.content.ContentServicePolicies.OnContentPropertyUpdatePolicy#onContentPropertyUpdate(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName, org.alfresco.service.cmr.repository.ContentData, org.alfresco.service.cmr.repository.ContentData) - */ - @Override - public void onContentPropertyUpdate(NodeRef nodeRef, QName propertyQName, ContentData beforeValue, - ContentData afterValue) - { - // Break out early if rules are not enabled - if (!areRulesEnabled()) - { - return; - } - - // Check the new content and make sure that we do indeed want to trigger the rule - if (propertyQName.equals(ContentModel.PROP_PREFERENCE_VALUES)) - { - return; - } - - // Check for new content - boolean newContent = beforeValue == null && afterValue != null; - - // Check the new content and make sure that we do indeed want to trigger the rule - if (newContent) - { - if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_NO_CONTENT)) - { - return; - } - - // Note: Don't use the ContentService.getReader() because we don't need access to the content - if (!ContentData.hasContent(afterValue)) - { - return; - } - } - // An update, but double check for content created in this transaction - else - { - Set newNodeRefSet = TransactionalResourceHelper.getSet(RULE_TRIGGER_NEW_NODES); - if (newNodeRefSet.contains(nodeRef)) - { - if (logger.isDebugEnabled()) - { - logger.debug("Receiving content property update for node created in transaction: " + nodeRef); - } - return; - } - } - - // Trigger the rules in the appropriate way - if (newContent == this.onNewContent) - { - if (triggerParentRules == true) - { - if (logger.isDebugEnabled() == true) - { - logger.debug("OnContentUpdate rule triggered fired for content; nodeId=" + nodeRef.getId() + "; newContent=" + newContent); - } - - List parentsAssocRefs = this.nodeService.getParentAssocs(nodeRef); - for (ChildAssociationRef parentAssocRef : parentsAssocRefs) - { - triggerRules(parentAssocRef.getParentRef(), nodeRef); - } - } - else - { - triggerRules(nodeRef, nodeRef); - } - } - } -} diff --git a/source/java/org/alfresco/repo/rule/ruletrigger/OnPropertyUpdateRuleTrigger.java b/source/java/org/alfresco/repo/rule/ruletrigger/OnPropertyUpdateRuleTrigger.java index 4ca79f884e..326f0bf13f 100644 --- a/source/java/org/alfresco/repo/rule/ruletrigger/OnPropertyUpdateRuleTrigger.java +++ b/source/java/org/alfresco/repo/rule/ruletrigger/OnPropertyUpdateRuleTrigger.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 * @@ -19,7 +19,7 @@ package org.alfresco.repo.rule.ruletrigger; import java.io.Serializable; -import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -27,10 +27,12 @@ import java.util.Set; import org.alfresco.model.ContentModel; import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.policy.JavaBehaviour; +import org.alfresco.repo.rule.RuntimeRuleService; import org.alfresco.repo.transaction.TransactionalResourceHelper; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.PropertyDefinition; import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; @@ -51,10 +53,39 @@ public class OnPropertyUpdateRuleTrigger extends RuleTriggerAbstractBase */ private static Log logger = LogFactory.getLog(OnPropertyUpdateRuleTrigger.class); - /** True trigger parent rules, false otherwier */ + /** True trigger on new content, false otherwise */ + private boolean onNewContent = false; + + /** Should we consider zero byte content to be the same as no content? */ + private boolean ignoreEmptyContent = true; + + /** True trigger parent rules, false otherwise */ private boolean triggerParentRules = true; + /** Runtime rule service */ + private RuntimeRuleService runtimeRuleService; + + /** + * If set to true the trigger will fire on new content, otherwise it will fire on content update + * + * @param onNewContent indicates whether to fire on content create or update + */ + public void setOnNewContent(boolean onNewContent) + { + this.onNewContent = onNewContent; + } + /** + * If set to true, then we consider zero byte content to be equivalent to no content. + * + * @param ignoreEmptyContent + */ + public void setIgnoreEmptyContent(boolean ignoreEmptyContent) + { + this.ignoreEmptyContent = ignoreEmptyContent; + } + + /** * Indicates whether the parent rules should be triggered or the rules on the node itself * * @param triggerParentRules true trigger parent rules, false otherwise @@ -64,6 +95,14 @@ public class OnPropertyUpdateRuleTrigger extends RuleTriggerAbstractBase this.triggerParentRules = triggerParentRules; } + /** + * Set the rule service + */ + public void setRuntimeRuleService(RuntimeRuleService runtimeRuleService) + { + this.runtimeRuleService = runtimeRuleService; + } + /* * @see org.alfresco.repo.rule.ruletrigger.RuleTrigger#registerRuleTrigger() */ @@ -76,48 +115,95 @@ public class OnPropertyUpdateRuleTrigger extends RuleTriggerAbstractBase new JavaBehaviour(this, "onUpdateProperties")); } - private boolean havePropertiesBeenModified(NodeRef nodeRef, Map before, Map after) + private boolean havePropertiesBeenModified(NodeRef nodeRef, Map before, Map after, boolean newNode, boolean newContentOnly) { - List remainder = new ArrayList(after.keySet()); - List modifiedProperties = new ArrayList(); - for (QName name : before.keySet()) + if (newContentOnly && nodeService.hasAspect(nodeRef, ContentModel.ASPECT_NO_CONTENT)) { - if (after.containsKey(name) == true) - { - Serializable beforeValue = before.get(name); - Serializable afterValue = after.get(name); - if (EqualsHelper.nullSafeEquals(beforeValue, afterValue) != true) - { - // The property has been changed - modifiedProperties.add(name); - } - - // Remove the property from the remainder list - remainder.remove(name); - } - } - - // Add any properties now remaining whose values have been added for the first time - if (remainder.size() != 0) - { - modifiedProperties.addAll(remainder); - } - - // Filter out the protected and content type properties from the list of modified properties - for (QName propertyName : new ArrayList(modifiedProperties)) - { - PropertyDefinition propertyDefinition = this.dictionaryService.getProperty(propertyName); - if (propertyDefinition != null) - { - if (propertyDefinition.isProtected() == true || propertyDefinition.getDataType().getName().equals(DataTypeDefinition.CONTENT) == true) - { - // Remove the protected property from the list - modifiedProperties.remove(propertyName); - } - } + return false; } - - return (modifiedProperties.isEmpty() == false); + + Set keys = new HashSet(after.keySet()); + keys.addAll(before.keySet()); + + // Compare all properties, ignoring protected properties and giving special treatment to content properties + boolean nonNullContentProperties = false; + boolean newContentProperties = false; + boolean nonNewModifiedContentProperties = false; + boolean modifiedNonContentProperties = false; + for (QName name : keys) + { + // Skip rule firing on this content property for performance reasons + if (name.equals(ContentModel.PROP_PREFERENCE_VALUES)) + { + continue; + } + Serializable beforeValue = before.get(name); + Serializable afterValue = after.get(name); + PropertyDefinition propertyDefinition = this.dictionaryService.getProperty(name); + if (propertyDefinition == null) + { + if (!EqualsHelper.nullSafeEquals(beforeValue, afterValue)) + { + modifiedNonContentProperties = true; + } + } + // Ignore protected properties + else if (!propertyDefinition.isProtected()) + { + if (propertyDefinition.getDataType().getName().equals(DataTypeDefinition.CONTENT)) + { + // Remember whether the property was populated, regardless of the ignore setting + if (afterValue != null) + { + nonNullContentProperties = true; + } + if (this.ignoreEmptyContent) + { + ContentData beforeContent = (ContentData) before.get(name); + ContentData afterContent = (ContentData) after.get(name); + if (!ContentData.hasContent(beforeContent) || beforeContent.getSize() == 0) + { + beforeValue = null; + } + if (!ContentData.hasContent(afterContent) || afterContent.getSize() == 0) + { + afterValue = null; + } + } + if (newNode) + { + if (afterValue != null) + { + newContentProperties = true; + } + } + else if (!EqualsHelper.nullSafeEquals(beforeValue, afterValue)) + { + if (beforeValue == null) + { + newContentProperties = true; + } + else + { + nonNewModifiedContentProperties = true; + } + } + } + else if (!EqualsHelper.nullSafeEquals(beforeValue, afterValue)) + { + modifiedNonContentProperties = true; + } + } + } + + if (newContentOnly) + { + return (newNode && !nonNullContentProperties ) || newContentProperties; + } + else + { + return modifiedNonContentProperties || nonNewModifiedContentProperties; + } } /** @@ -133,47 +219,87 @@ public class OnPropertyUpdateRuleTrigger extends RuleTriggerAbstractBase // Do not fire if the node has been created in this transaction Set newNodeRefSet = TransactionalResourceHelper.getSet(RULE_TRIGGER_NEW_NODES); boolean wasCreatedInTxn = newNodeRefSet.contains(nodeRef); - if (logger.isDebugEnabled() && wasCreatedInTxn) + if (wasCreatedInTxn) { - logger.debug("Receiving property update for node created in transaction: " + nodeRef); - } - - // Only try and trigger the rules if a non protected property has been modified - if (!wasCreatedInTxn && - before.size() != 0 && // ALF-4846: Do not trigger for newly created nodes - havePropertiesBeenModified(nodeRef, before, after) == true) - { - // Keep track of name changes explicitly. This prevents the later association change from - // triggering 'inbound' rules - if (!EqualsHelper.nullSafeEquals(before.get(ContentModel.PROP_NAME), after.get(ContentModel.PROP_NAME))) + if (logger.isDebugEnabled()) { - // Name has changed - Set renamedNodeRefSet = TransactionalResourceHelper.getSet(RULE_TRIGGER_RENAMED_NODES); - renamedNodeRefSet.add(nodeRef); + logger.debug("Receiving property update for node created in transaction: " + nodeRef); } - if (triggerParentRules == true) - { - List parentsAssocRefs = this.nodeService.getParentAssocs(nodeRef); - for (ChildAssociationRef parentAssocRef : parentsAssocRefs) + + // A rule has already been fired for this new node, but now that we are aware of content properties, we may + // want to withhold it until later + if (this.onNewContent) + { + if (havePropertiesBeenModified(nodeRef, before, after, true, true)) { - triggerRules(parentAssocRef.getParentRef(), nodeRef); + // Possibly undo a previous cancellation in this transaction if (logger.isDebugEnabled() == true) { - logger.debug( - "OnPropertyUpdate rule triggered (parent); " + - "nodeRef=" + parentAssocRef.getParentRef()); + logger.debug("New node " + nodeRef.toString() + + " confirmed to have no content properties or to have new content so firing inbound rules."); } + triggerRules(nodeRef); + } + else + { + // Removes any rules that have already been triggered for that node + if (logger.isDebugEnabled() == true) + { + logger.debug("Removing the pending rules for the new node " + nodeRef.toString() + + " since there are no non-empty content properties."); + } + runtimeRuleService.removeRulePendingExecution(nodeRef); } } - else + } + else + { + // Only try and trigger the rules if a non protected property has been modified + if (!wasCreatedInTxn && + before.size() != 0 && // ALF-4846: Do not trigger for newly created nodes + havePropertiesBeenModified(nodeRef, before, after, false, this.onNewContent)) { - triggerRules(nodeRef, nodeRef); + // Keep track of name changes explicitly. This prevents the later association change from + // triggering 'inbound' rules + if (!EqualsHelper.nullSafeEquals(before.get(ContentModel.PROP_NAME), after.get(ContentModel.PROP_NAME))) + { + // Name has changed + Set renamedNodeRefSet = TransactionalResourceHelper.getSet(RULE_TRIGGER_RENAMED_NODES); + renamedNodeRefSet.add(nodeRef); + } + + triggerRules(nodeRef); + } + } + } + + /** + * @param nodeRef + */ + private void triggerRules(NodeRef nodeRef) + { + if (triggerParentRules == true) + { + List parentsAssocRefs = this.nodeService.getParentAssocs(nodeRef); + for (ChildAssociationRef parentAssocRef : parentsAssocRefs) + { + triggerRules(parentAssocRef.getParentRef(), nodeRef); if (logger.isDebugEnabled() == true) { - logger.debug("OnPropertyUpdate rule triggered; nodeRef=" + nodeRef); + logger.debug( + "OnPropertyUpdate rule triggered (parent); " + + "nodeRef=" + parentAssocRef.getParentRef()); } } } + else + { + triggerRules(nodeRef, nodeRef); + if (logger.isDebugEnabled() == true) + { + logger.debug("OnPropertyUpdate rule triggered; nodeRef=" + nodeRef); + } + } } } diff --git a/source/java/org/alfresco/repo/rule/ruletrigger/RuleTriggerTest.java b/source/java/org/alfresco/repo/rule/ruletrigger/RuleTriggerTest.java index 67f1f77346..63219a3463 100644 --- a/source/java/org/alfresco/repo/rule/ruletrigger/RuleTriggerTest.java +++ b/source/java/org/alfresco/repo/rule/ruletrigger/RuleTriggerTest.java @@ -44,7 +44,7 @@ public class RuleTriggerTest extends BaseSpringTest private static final String ON_DELETE_CHILD_ASSOCIATION_TRIGGER = "on-delete-child-association-trigger"; private static final String ON_CREATE_ASSOCIATION_TRIGGER = "on-create-association-trigger"; private static final String ON_DELETE_ASSOCIATION_TRIGGER = "on-delete-association-trigger"; - private static final String ON_CONTENT_UPDATE_TRIGGER = "on-content-update-trigger"; + private static final String ON_PROPERTY_UPDATE_TRIGGER = "on-property-update-trigger"; private static final String ON_CONTENT_CREATE_TRIGGER = "on-content-create-trigger"; private NodeService nodeService; @@ -228,12 +228,22 @@ public class RuleTriggerTest extends BaseSpringTest public void testOnContentCreateTrigger() { + TestRuleType nodeCreate = createTestRuleType(ON_CREATE_NODE_TRIGGER); + assertFalse(nodeCreate.rulesTriggered); + NodeRef nodeRef = this.nodeService.createNode( this.rootNodeRef, ContentModel.ASSOC_CHILDREN, ContentModel.ASSOC_CHILDREN, ContentModel.TYPE_CONTENT).getChildRef(); + assertTrue(nodeCreate.rulesTriggered); + + // Terminate the transaction + setComplete(); + endTransaction(); + startNewTransaction(); + TestRuleType contentCreate = createTestRuleType(ON_CONTENT_CREATE_TRIGGER); assertFalse(contentCreate.rulesTriggered); @@ -260,14 +270,19 @@ public class RuleTriggerTest extends BaseSpringTest public void testOnContentUpdateTrigger() { - NodeRef nodeRef = this.nodeService.createNode( + TestRuleType nodeCreate = createTestRuleType(ON_CREATE_NODE_TRIGGER); + assertFalse(nodeCreate.rulesTriggered); + + NodeRef nodeRef = this.nodeService.createNode( this.rootNodeRef, ContentModel.ASSOC_CHILDREN, ContentModel.ASSOC_CHILDREN, ContentModel.TYPE_CONTENT).getChildRef(); + + assertTrue(nodeCreate.rulesTriggered); TestRuleType contentCreate = createTestRuleType(ON_CONTENT_CREATE_TRIGGER); - TestRuleType contentUpdate = createTestRuleType(ON_CONTENT_UPDATE_TRIGGER); + TestRuleType contentUpdate = createTestRuleType(ON_PROPERTY_UPDATE_TRIGGER); assertFalse(contentCreate.rulesTriggered); assertFalse(contentUpdate.rulesTriggered); @@ -290,7 +305,7 @@ public class RuleTriggerTest extends BaseSpringTest contentWriter2.putContent("more content some content"); // Check to see if the rule type has been triggered - assertFalse(contentCreate.rulesTriggered); + assertTrue(contentCreate.rulesTriggered); assertFalse( "Content update must not fire if the content was created in the same txn.", contentUpdate.rulesTriggered); @@ -298,6 +313,7 @@ public class RuleTriggerTest extends BaseSpringTest // Terminate the transaction setComplete(); endTransaction(); + contentCreate.rulesTriggered = false; // Try and trigger the type (again) ContentWriter contentWriter3 = this.contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true); diff --git a/source/java/org/alfresco/repo/search/impl/solr/SolrBackupClient.java b/source/java/org/alfresco/repo/search/impl/solr/SolrBackupClient.java index 46cba5df10..649951d866 100644 --- a/source/java/org/alfresco/repo/search/impl/solr/SolrBackupClient.java +++ b/source/java/org/alfresco/repo/search/impl/solr/SolrBackupClient.java @@ -89,7 +89,7 @@ public class SolrBackupClient implements InitializingBean String lockToken = getLock(60000); if (lockToken == null) { - + return; } // Use a flag to keep track of the running job final AtomicBoolean running = new AtomicBoolean(true); diff --git a/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceImpl.java b/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceImpl.java index 16b9161f80..c4f2d78a10 100644 --- a/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceImpl.java +++ b/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceImpl.java @@ -765,7 +765,7 @@ public class PermissionServiceImpl extends AbstractLifecycleBean implements Perm * Key for a cache object is built from all the known Authorities (which can change dynamically so they must all be * used) the NodeRef ID and the permission reference itself. This gives a unique key for each permission test. */ - static Serializable generateKey(Set auths, NodeRef nodeRef, PermissionReference perm, CacheType type) + Serializable generateKey(Set auths, NodeRef nodeRef, PermissionReference perm, CacheType type) { LinkedHashSet key = new LinkedHashSet(); key.add(perm.toString()); @@ -779,6 +779,9 @@ public class PermissionServiceImpl extends AbstractLifecycleBean implements Perm key.addAll(auths); } key.add(nodeRef); + // Ensure some concept of node version or transaction is included in the key so we can track without cache replication + NodeRef.Status nodeStatus = nodeService.getNodeStatus(nodeRef); + key.add(nodeStatus == null ? "null" : nodeStatus.getChangeTxnId()); key.add(type); return key; } @@ -1177,22 +1180,6 @@ public class PermissionServiceImpl extends AbstractLifecycleBean implements Perm */ @Override public Set getReaders(Long aclId) - { - Set aclReaders = readersCache.get(aclId); - if (aclReaders == null) - { - aclReaders = buildReaders(aclId); - readersCache.put(aclId, aclReaders); - } - return aclReaders; - } - - /** - * Builds the set of authorities who can read the given ACL. No caching is done here. - * - * @return an unmodifiable set of authorities - */ - protected Set buildReaders(Long aclId) { AccessControlList acl = aclDaoComponent.getAccessControlList(aclId); if (acl == null) @@ -1200,6 +1187,12 @@ public class PermissionServiceImpl extends AbstractLifecycleBean implements Perm return Collections.emptySet(); } + Set aclReaders = readersCache.get((Serializable)acl.getProperties()); + if (aclReaders != null) + { + return aclReaders; + } + HashSet assigned = new HashSet(); HashSet readers = new HashSet(); @@ -1217,23 +1210,30 @@ public class PermissionServiceImpl extends AbstractLifecycleBean implements Perm } } - return Collections.unmodifiableSet(readers); + aclReaders = Collections.unmodifiableSet(readers); + readersCache.put((Serializable)acl.getProperties(), aclReaders); + return aclReaders; } /** * @param aclId * @return set of authorities with read permission on the ACL */ - protected Set buildReadersDenied(Long aclId) + private Set getReadersDenied(Long aclId) { - HashSet assigned = new HashSet(); - HashSet denied = new HashSet(); AccessControlList acl = aclDaoComponent.getAccessControlList(aclId); if (acl == null) + { + return Collections.emptySet(); + } + Set denied = readersDeniedCache.get(aclId); + if (denied != null) { return denied; } + denied = new HashSet(); + Set assigned = new HashSet(); for (AccessControlEntry ace : acl.getEntries()) { @@ -1248,6 +1248,8 @@ public class PermissionServiceImpl extends AbstractLifecycleBean implements Perm denied.add(authority); } } + + readersDeniedCache.put((Serializable)acl.getProperties(), denied); return denied; } @@ -1261,12 +1263,7 @@ public class PermissionServiceImpl extends AbstractLifecycleBean implements Perm if(anyDenyDenies) { - Set aclReadersDenied = readersDeniedCache.get(aclId); - if(aclReadersDenied == null) - { - aclReadersDenied = buildReadersDenied(aclId); - readersDeniedCache.put(aclId, aclReadersDenied); - } + Set aclReadersDenied = getReadersDenied(aclId); for(String auth : aclReadersDenied) { diff --git a/source/java/org/alfresco/repo/security/person/HomeFolderProviderSynchronizer.java b/source/java/org/alfresco/repo/security/person/HomeFolderProviderSynchronizer.java index 3177774ca4..22a1d614ae 100644 --- a/source/java/org/alfresco/repo/security/person/HomeFolderProviderSynchronizer.java +++ b/source/java/org/alfresco/repo/security/person/HomeFolderProviderSynchronizer.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 * @@ -894,6 +894,19 @@ public class HomeFolderProviderSynchronizer extends AbstractLifecycleBean // BatchProcessWorker that runs work as another user. private abstract class RunAsWorker extends BatchProcessWorkerAdaptor { + @Override + public void beforeProcess() throws Throwable + { + AuthenticationUtil.pushAuthentication(); + AuthenticationUtil.setFullyAuthenticatedUser(userName); + } + + @Override + public void afterProcess() throws Throwable + { + AuthenticationUtil.popAuthentication(); + } + final String userName; final String name; diff --git a/source/java/org/alfresco/repo/solr/SOLRTrackingComponentImpl.java b/source/java/org/alfresco/repo/solr/SOLRTrackingComponentImpl.java index 78702b488c..83aefb68f0 100644 --- a/source/java/org/alfresco/repo/solr/SOLRTrackingComponentImpl.java +++ b/source/java/org/alfresco/repo/solr/SOLRTrackingComponentImpl.java @@ -186,6 +186,9 @@ public class SOLRTrackingComponentImpl implements SOLRTrackingComponent { if(enabled) { + // We don't want the caches to lie and we may not be part of the cluster + aclDAO.setCheckAclConsistency(); + /* * This is an N+1 query that should, in theory, make use of cached ACL readers data. */ @@ -486,7 +489,6 @@ public class SOLRTrackingComponentImpl implements SOLRTrackingComponent private List preCacheNodes(NodeMetaDataParameters nodeMetaDataParameters) { - nodeDAO.setCheckNodeConsistency(); int maxResults = nodeMetaDataParameters.getMaxResults(); boolean isLimitSet = (maxResults != 0 && maxResults != Integer.MAX_VALUE); @@ -515,8 +517,13 @@ public class SOLRTrackingComponentImpl implements SOLRTrackingComponent nodeIds.add(nodeId); } } - // pre-cache nodes - nodeDAO.cacheNodesById(nodeIds); + + // Pre-evaluate ancestors so we can bulk load them + List ancestors = new ArrayList(nodeDAO.getCachedAncestors(nodeIds)); + // Ensure that we get fresh node references + nodeDAO.setCheckNodeConsistency(); + // bulk load nodes and their ancestors + nodeDAO.cacheNodesById(ancestors); return nodeIds; } @@ -554,10 +561,7 @@ public class SOLRTrackingComponentImpl implements SOLRTrackingComponent { return; } - - // Ensure that we get fresh node references - nodeDAO.setCheckNodeConsistency(); - + NodeMetaDataQueryRowHandler rowHandler = new NodeMetaDataQueryRowHandler(callback); boolean includeType = (resultFilter == null ? true : resultFilter.getIncludeType()); boolean includeProperties = (resultFilter == null ? true : resultFilter.getIncludeProperties());