Dave Ward 182c29aac2 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
2012-11-23 16:46:57 +00:00

1901 lines
65 KiB
Java

/*
* 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 <http://www.gnu.org/licenses/>.
*/
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;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.repo.cache.SimpleCache;
import org.alfresco.repo.domain.node.NodeDAO;
import org.alfresco.repo.domain.qname.QNameDAO;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.permissions.ACEType;
import org.alfresco.repo.security.permissions.ACLCopyMode;
import org.alfresco.repo.security.permissions.ACLType;
import org.alfresco.repo.security.permissions.AccessControlEntry;
import org.alfresco.repo.security.permissions.AccessControlList;
import org.alfresco.repo.security.permissions.AccessControlListProperties;
import org.alfresco.repo.security.permissions.SimpleAccessControlEntry;
import org.alfresco.repo.security.permissions.SimpleAccessControlList;
import org.alfresco.repo.security.permissions.SimpleAccessControlListProperties;
import org.alfresco.repo.security.permissions.impl.AclChange;
import org.alfresco.repo.security.permissions.impl.SimplePermissionReference;
import org.alfresco.repo.tenant.TenantService;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.TransactionListenerAdapter;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.security.AccessStatus;
import org.alfresco.service.cmr.security.AuthorityType;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.GUID;
import org.alfresco.util.Pair;
import org.alfresco.util.ParameterCheck;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* DAO to manage ACL persistence
*
* Note: based on earlier AclDaoComponentImpl
*
* @author Andy Hind, janv
* @since 3.4
*/
public class AclDAOImpl implements AclDAO
{
private static Log logger = LogFactory.getLog(AclDAOImpl.class);
private QNameDAO qnameDAO;
private AclCrudDAO aclCrudDAO;
private NodeDAO nodeDAO;
private TenantService tenantService;
private SimpleCache<Serializable, AccessControlList> aclCache;
private enum WriteMode
{
/**
* Remove inherited ACEs after that set
*/
TRUNCATE_INHERITED,
/**
* Add inherited ACEs
*/
ADD_INHERITED,
/**
* The source of inherited ACEs is changing
*/
CHANGE_INHERITED,
/**
* Remove all inherited ACEs
*/
REMOVE_INHERITED,
/**
* Insert inherited ACEs
*/
INSERT_INHERITED,
/**
* Copy ACLs and update ACEs and inheritance
*/
COPY_UPDATE_AND_INHERIT,
/**
* Simple copy
*/
COPY_ONLY, CREATE_AND_INHERIT;
}
public void setQnameDAO(QNameDAO qnameDAO)
{
this.qnameDAO = qnameDAO;
}
public void setTenantService(TenantService tenantService)
{
this.tenantService = tenantService;
}
public void setAclCrudDAO(AclCrudDAO aclCrudDAO)
{
this.aclCrudDAO = aclCrudDAO;
}
public void setNodeDAO(NodeDAO nodeDAO)
{
this.nodeDAO = nodeDAO;
}
/**
* Set the ACL cache
*
* @param aclCache
*/
public void setAclCache(SimpleCache<Serializable, AccessControlList> aclCache)
{
this.aclCache = aclCache;
}
/**
* {@inheritDoc}
*/
@Override
public Long createAccessControlList()
{
return createAccessControlList(getDefaultProperties()).getId();
}
/**
* {@inheritDoc}
*/
@Override
public AccessControlListProperties getDefaultProperties()
{
SimpleAccessControlListProperties properties = new SimpleAccessControlListProperties();
properties.setAclType(ACLType.DEFINING);
properties.setInherits(true);
properties.setVersioned(false);
return properties;
}
/**
* {@inheritDoc}
*/
@Override
public Acl createAccessControlList(AccessControlListProperties properties)
{
if (properties == null)
{
throw new IllegalArgumentException("Properties cannot be null");
}
if (properties.getAclType() == null)
{
throw new IllegalArgumentException("ACL Type must be defined");
}
switch (properties.getAclType())
{
case OLD:
if (properties.isVersioned() == Boolean.TRUE)
{
throw new IllegalArgumentException("Old acls can not be versioned");
}
break;
case SHARED:
throw new IllegalArgumentException("Can not create shared acls direct - use get inherited");
case DEFINING:
case LAYERED:
break;
case FIXED:
if (properties.getInherits() == Boolean.TRUE)
{
throw new IllegalArgumentException("Fixed ACLs can not inherit");
}
case GLOBAL:
if (properties.getInherits() == Boolean.TRUE)
{
throw new IllegalArgumentException("Fixed ACLs can not inherit");
}
default:
break;
}
return createAccessControlList(properties, null, null);
}
/**
* {@inheritDoc}
*/
@Override
public Acl createAccessControlList(AccessControlListProperties properties, List<AccessControlEntry> aces, Long inherited)
{
if (properties == null)
{
throw new IllegalArgumentException("Properties cannot be null");
}
AclEntity acl = new AclEntity();
if (properties.getAclId() != null)
{
acl.setAclId(properties.getAclId());
}
else
{
acl.setAclId(GUID.generate());
}
acl.setAclType(properties.getAclType());
acl.setAclVersion(Long.valueOf(1l));
switch (properties.getAclType())
{
case FIXED:
case GLOBAL:
acl.setInherits(Boolean.FALSE);
case OLD:
case SHARED:
case DEFINING:
case LAYERED:
default:
if (properties.getInherits() != null)
{
acl.setInherits(properties.getInherits());
}
else
{
acl.setInherits(Boolean.TRUE);
}
break;
}
acl.setLatest(Boolean.TRUE);
switch (properties.getAclType())
{
case OLD:
acl.setVersioned(Boolean.FALSE);
break;
case LAYERED:
if (properties.isVersioned() != null)
{
acl.setVersioned(properties.isVersioned());
}
else
{
acl.setVersioned(Boolean.TRUE);
}
break;
case FIXED:
case GLOBAL:
case SHARED:
case DEFINING:
default:
if (properties.isVersioned() != null)
{
acl.setVersioned(properties.isVersioned());
}
else
{
acl.setVersioned(Boolean.FALSE);
}
break;
}
acl.setAclChangeSetId(getCurrentChangeSetId());
acl.setRequiresVersion(false);
Acl createdAcl = (AclEntity)aclCrudDAO.createAcl(acl);
long created = createdAcl.getId();
List<Ace> toAdd = new ArrayList<Ace>();
List<AccessControlEntry> excluded = new ArrayList<AccessControlEntry>();
List<AclChange> changes = new ArrayList<AclChange>();
if ((aces != null) && aces.size() > 0)
{
for (AccessControlEntry ace : aces)
{
if ((ace.getPosition() != null) && (ace.getPosition() != 0))
{
throw new IllegalArgumentException("Invalid position");
}
// Find authority
Authority authority = aclCrudDAO.getOrCreateAuthority(ace.getAuthority());
Permission permission = aclCrudDAO.getOrCreatePermission(ace.getPermission());
// Find context
if (ace.getContext() != null)
{
throw new UnsupportedOperationException();
}
// Find ACE
Ace entry = aclCrudDAO.getOrCreateAce(permission, authority, ace.getAceType(), ace.getAccessStatus());
// Wire up
// COW and remove any existing matches
SimpleAccessControlEntry exclude = new SimpleAccessControlEntry();
// match any access status
exclude.setAceType(ace.getAceType());
exclude.setAuthority(ace.getAuthority());
exclude.setPermission(ace.getPermission());
exclude.setPosition(0);
toAdd.add(entry);
excluded.add(exclude);
// Will remove from the cache
}
}
Long toInherit = null;
if (inherited != null)
{
toInherit = getInheritedAccessControlList(inherited);
}
getWritable(created, toInherit, excluded, toAdd, toInherit, false, changes, WriteMode.CREATE_AND_INHERIT);
// Fetch an up-to-date version
return getAcl(created);
}
private void getWritable(
final Long id, final Long parent,
List<? extends AccessControlEntry> exclude, List<Ace> toAdd,
Long inheritsFrom, boolean cascade,
List<AclChange> changes, WriteMode mode)
{
List<Ace> inherited = null;
List<Integer> positions = null;
if ((mode == WriteMode.ADD_INHERITED) || (mode == WriteMode.INSERT_INHERITED) || (mode == WriteMode.CHANGE_INHERITED) || (mode == WriteMode.CREATE_AND_INHERIT ))
{
inherited = new ArrayList<Ace>();
positions = new ArrayList<Integer>();
// get aces for acl (via acl member)
List<AclMember> members;
if(parent != null)
{
members = aclCrudDAO.getAclMembersByAcl(parent);
}
else
{
members = Collections.<AclMember>emptyList();
}
for (AclMember member : members)
{
Ace aceEntity = aclCrudDAO.getAce(member.getAceId());
if ((mode == WriteMode.INSERT_INHERITED) && (member.getPos() == 0))
{
inherited.add(aceEntity);
positions.add(member.getPos());
}
else
{
inherited.add(aceEntity);
positions.add(member.getPos());
}
}
}
getWritable(id, parent, exclude, toAdd, inheritsFrom, inherited, positions, cascade, 0, changes, mode, false);
}
/**
* Make a whole tree of ACLs copy on write if required Includes adding and removing ACEs which can be optimised
* slightly for copy on write (no need to add and then remove)
*/
private void getWritable(
final Long id, final Long parent,
List<? extends AccessControlEntry> exclude, List<Ace> toAdd, Long inheritsFrom,
List<Ace> inherited, List<Integer> positions,
boolean cascade, int depth, List<AclChange> changes, WriteMode mode, boolean requiresVersion)
{
AclChange current = getWritable(id, parent, exclude, toAdd, inheritsFrom, inherited, positions, depth, mode, requiresVersion);
changes.add(current);
boolean cascadeVersion = requiresVersion;
if (!cascadeVersion)
{
cascadeVersion = !current.getBefore().equals(current.getAfter());
}
if (cascade)
{
List<Long> inheritors = aclCrudDAO.getAclsThatInheritFromAcl(id);
for (Long nextId : inheritors)
{
// Check for those that inherit themselves to other nodes ...
if (!nextId.equals(id))
{
getWritable(nextId, current.getAfter(), exclude, toAdd, current.getAfter(), inherited, positions, cascade, depth + 1, changes, mode, cascadeVersion);
}
}
}
}
/**
* COW for an individual ACL
* @return - an AclChange
*/
private AclChange getWritable(
final Long id, final Long parent,
List<? extends AccessControlEntry> exclude, List<Ace> acesToAdd, Long inheritsFrom,
List<Ace> inherited, List<Integer> positions, int depth, WriteMode mode, boolean requiresVersion)
{
AclUpdateEntity acl = aclCrudDAO.getAclForUpdate(id);
if (!acl.isLatest())
{
return new AclChangeImpl(id, id, acl.getAclType(), acl.getAclType());
}
List<Long> toAdd = new ArrayList<Long>(0);
if (acesToAdd != null)
{
for (Ace ace : acesToAdd)
{
toAdd.add(ace.getId());
}
}
if (!acl.isVersioned())
{
switch (mode)
{
case COPY_UPDATE_AND_INHERIT:
removeAcesFromAcl(id, exclude, depth);
aclCrudDAO.addAclMembersToAcl(acl.getId(), toAdd, depth);
break;
case CHANGE_INHERITED:
replaceInherited(id, acl, inherited, positions, depth);
break;
case ADD_INHERITED:
addInherited(acl, inherited, positions, depth);
break;
case TRUNCATE_INHERITED:
truncateInherited(id, depth);
break;
case INSERT_INHERITED:
insertInherited(id, acl, inherited, positions, depth);
break;
case REMOVE_INHERITED:
removeInherited(id, depth);
break;
case CREATE_AND_INHERIT:
aclCrudDAO.addAclMembersToAcl(acl.getId(), toAdd, depth);
addInherited(acl, inherited, positions, depth);
case COPY_ONLY:
default:
break;
}
if (inheritsFrom != null)
{
acl.setInheritsFrom(inheritsFrom);
}
acl.setAclChangeSetId(getCurrentChangeSetId());
aclCrudDAO.updateAcl(acl);
return new AclChangeImpl(id, id, acl.getAclType(), acl.getAclType());
}
else if ((acl.getAclChangeSetId() == getCurrentChangeSetId()) && (!requiresVersion) && (!acl.getRequiresVersion()))
{
switch (mode)
{
case COPY_UPDATE_AND_INHERIT:
removeAcesFromAcl(id, exclude, depth);
aclCrudDAO.addAclMembersToAcl(acl.getId(), toAdd, depth);
break;
case CHANGE_INHERITED:
replaceInherited(id, acl, inherited, positions, depth);
break;
case ADD_INHERITED:
addInherited(acl, inherited, positions, depth);
break;
case TRUNCATE_INHERITED:
truncateInherited(id, depth);
break;
case INSERT_INHERITED:
insertInherited(id, acl, inherited, positions, depth);
break;
case REMOVE_INHERITED:
removeInherited(id, depth);
break;
case CREATE_AND_INHERIT:
aclCrudDAO.addAclMembersToAcl(acl.getId(), toAdd, depth);
addInherited(acl, inherited, positions, depth);
case COPY_ONLY:
default:
break;
}
if (inheritsFrom != null)
{
acl.setInheritsFrom(inheritsFrom);
}
aclCrudDAO.updateAcl(acl);
return new AclChangeImpl(id, id, acl.getAclType(), acl.getAclType());
}
else
{
AclEntity newAcl = new AclEntity();
newAcl.setAclChangeSetId(getCurrentChangeSetId());
newAcl.setAclId(acl.getAclId());
newAcl.setAclType(acl.getAclType());
newAcl.setAclVersion(acl.getAclVersion() + 1);
newAcl.setInheritedAcl(-1l);
newAcl.setInherits(acl.getInherits());
newAcl.setInheritsFrom((inheritsFrom != null) ? inheritsFrom : acl.getInheritsFrom());
newAcl.setLatest(Boolean.TRUE);
newAcl.setVersioned(Boolean.TRUE);
newAcl.setRequiresVersion(Boolean.FALSE);
AclEntity createdAcl = (AclEntity)aclCrudDAO.createAcl(newAcl);
long created = createdAcl.getId();
// Create new membership entries - excluding those in the given pattern
// AcePatternMatcher excluder = new AcePatternMatcher(exclude);
// get aces for acl (via acl member)
List<AclMember> members = aclCrudDAO.getAclMembersByAcl(id);
if (members.size() > 0)
{
List<Pair<Long,Integer>> aceIdsWithDepths = new ArrayList<Pair<Long,Integer>>(members.size());
for (AclMember member : members)
{
aceIdsWithDepths.add(new Pair<Long, Integer>(member.getAceId(), member.getPos()));
}
// copy acl members to new acl
aclCrudDAO.addAclMembersToAcl(newAcl.getId(), aceIdsWithDepths);
}
// add new
switch (mode)
{
case COPY_UPDATE_AND_INHERIT:
// Done above
removeAcesFromAcl(newAcl.getId(), exclude, depth);
aclCrudDAO.addAclMembersToAcl(newAcl.getId(), toAdd, depth);
break;
case CHANGE_INHERITED:
replaceInherited(newAcl.getId(), newAcl, inherited, positions, depth);
break;
case ADD_INHERITED:
addInherited(newAcl, inherited, positions, depth);
break;
case TRUNCATE_INHERITED:
truncateInherited(newAcl.getId(), depth);
break;
case INSERT_INHERITED:
insertInherited(newAcl.getId(), newAcl, inherited, positions, depth);
break;
case REMOVE_INHERITED:
removeInherited(newAcl.getId(), depth);
break;
case CREATE_AND_INHERIT:
aclCrudDAO.addAclMembersToAcl(acl.getId(), toAdd, depth);
addInherited(acl, inherited, positions, depth);
case COPY_ONLY:
default:
break;
}
// Fix up inherited ACL if required
if (newAcl.getAclType() == ACLType.SHARED)
{
if (parent != null)
{
Long writableParentAcl = getWritable(parent, null, null, null, null, null, null, 0, WriteMode.COPY_ONLY, false).getAfter();
AclUpdateEntity parentAcl = aclCrudDAO.getAclForUpdate(writableParentAcl);
parentAcl.setInheritedAcl(created);
aclCrudDAO.updateAcl(parentAcl);
}
}
// fix up old version
acl.setLatest(Boolean.FALSE);
acl.setRequiresVersion(Boolean.FALSE);
aclCrudDAO.updateAcl(acl);
return new AclChangeImpl(id, created, acl.getAclType(), newAcl.getAclType());
}
}
/**
* Helper to remove ACEs from an ACL
*/
private void removeAcesFromAcl(final Long id, final List<? extends AccessControlEntry> exclude, final int depth)
{
if (exclude == null)
{
// cascade delete all acl members - no exclusion
aclCrudDAO.deleteAclMembersByAcl(id);
}
else
{
AcePatternMatcher excluder = new AcePatternMatcher(exclude);
List<Map<String, Object>> results = aclCrudDAO.getAcesAndAuthoritiesByAcl(id);
List<Long> memberIds = new ArrayList<Long>(results.size());
for (Map<String, Object> result : results)
{
Long result_aclmemId = (Long) result.get("aclmemId");
if ((exclude != null) && excluder.matches(aclCrudDAO, result, depth))
{
memberIds.add(result_aclmemId);
}
}
// delete list of acl members
aclCrudDAO.deleteAclMembers(memberIds);
}
}
private void replaceInherited(Long id, Acl acl, List<Ace> inherited, List<Integer> positions, int depth)
{
truncateInherited(id, depth);
addInherited(acl, inherited, positions, depth);
}
private void truncateInherited(final Long id, int depth)
{
List<AclMember> members = aclCrudDAO.getAclMembersByAcl(id);
List<Long> membersToDelete = new ArrayList<Long>(members.size());
for (AclMember member : members)
{
if (member.getPos() > depth)
{
membersToDelete.add(member.getId());
}
}
if (membersToDelete.size() > 0)
{
// delete list of acl members
aclCrudDAO.deleteAclMembers(membersToDelete);
}
}
private void removeInherited(final Long id, int depth)
{
List<AclMemberEntity> members = aclCrudDAO.getAclMembersByAclForUpdate(id);
List<Long> membersToDelete = new ArrayList<Long>(members.size());
for (AclMemberEntity member : members)
{
if (member.getPos() == depth + 1)
{
membersToDelete.add(member.getId());
}
else if (member.getPos() > (depth + 1))
{
member.setPos(member.getPos() - 1);
aclCrudDAO.updateAclMember(member);
}
}
if (membersToDelete.size() > 0)
{
// delete list of acl members
aclCrudDAO.deleteAclMembers(membersToDelete);
}
}
private void addInherited(Acl acl, List<Ace> inherited, List<Integer> positions, int depth)
{
if ((inherited != null) && (inherited.size() > 0))
{
List<Pair<Long,Integer>> aceIdsWithDepths = new ArrayList<Pair<Long,Integer>>(inherited.size());
for (int i = 0; i < inherited.size(); i++)
{
Ace add = inherited.get(i);
Integer position = positions.get(i);
aceIdsWithDepths.add(new Pair<Long, Integer>(add.getId(), position.intValue() + depth + 1));
}
aclCrudDAO.addAclMembersToAcl(acl.getId(), aceIdsWithDepths);
}
}
private void insertInherited(final Long id, AclEntity acl, List<Ace> inherited, List<Integer> positions, int depth)
{
// get aces for acl (via acl member)
List<AclMemberEntity> members = aclCrudDAO.getAclMembersByAclForUpdate(id);
for (AclMemberEntity member : members)
{
if (member.getPos() > depth)
{
member.setPos(member.getPos() + 1);
aclCrudDAO.updateAclMember(member);
}
}
addInherited(acl, inherited, positions, depth);
}
/**
* {@inheritDoc}
*/
@Override
public List<AclChange> deleteAccessControlEntries(final String authority)
{
List<AclChange> aclChanges = new LinkedList<AclChange>();
// get authority
Authority authEntity = aclCrudDAO.getAuthority(authority);
if (authEntity == null)
{
return aclChanges;
}
List<Long> aces = new ArrayList<Long>();
List<AclMember> members = aclCrudDAO.getAclMembersByAuthority(authority);
boolean leaveAuthority = false;
if (members.size() > 0)
{
Set<AclUpdateEntity> acls = new HashSet<AclUpdateEntity>(members.size() * 2);
List<Long> membersToDelete = new ArrayList<Long>(members.size());
// fix up members and extract acls and aces
for (AclMember member : members)
{
// Delete acl entry
Long aclMemberId = member.getId();
Long aclId = member.getAclId();
Long aceId = member.getAceId();
boolean hasAnotherTenantNodes = false;
if (AuthenticationUtil.isMtEnabled())
{
// ALF-3563
// Retrieve dependent nodes
List<Long> nodeIds = aclCrudDAO.getADMNodesByAcl(aclId, -1);
nodeIds.addAll(aclCrudDAO.getAVMNodesByAcl(aclId, -1));
if (nodeIds.size() > 0)
{
for (Long nodeId : nodeIds)
{
Pair<Long, NodeRef> nodePair = nodeDAO.getNodePair(nodeId);
if (nodePair == null)
{
logger.warn("Node does not exist: " + nodeId);
continue;
}
else
{
NodeRef nodeRef = nodePair.getSecond();
try
{
// Throws AlfrescoRuntimeException in case of domain mismatch
tenantService.checkDomain(nodeRef.getStoreRef().getIdentifier());
}
catch (AlfrescoRuntimeException e)
{
hasAnotherTenantNodes = true;
leaveAuthority = true;
break;
}
}
}
}
}
if (!hasAnotherTenantNodes)
{
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);
}
}
// 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)
{
// remove ACEs
aclCrudDAO.deleteAces(aces);
// Tidy up any unreferenced ACEs
// get aces by authority
List<Ace> unreferenced = aclCrudDAO.getAcesByAuthority(authEntity.getId());
if (unreferenced.size() > 0)
{
List<Long> unrefencedAcesToDelete = new ArrayList<Long>(unreferenced.size());
for (Ace ace : unreferenced)
{
unrefencedAcesToDelete.add(ace.getId());
}
aclCrudDAO.deleteAces(unrefencedAcesToDelete);
}
// remove authority
if (authEntity != null)
{
aclCrudDAO.deleteAuthority(authEntity.getId());
}
}
return aclChanges;
}
/**
* {@inheritDoc}
*/
@Override
public void deleteAclForNode(long aclId, boolean isAVMNode)
{
Acl dbAcl = getAcl(aclId);
if (dbAcl.getAclType() == ACLType.DEFINING)
{
// delete acl members & acl
aclCrudDAO.deleteAclMembersByAcl(aclId);
aclCrudDAO.deleteAcl(aclId);
}
if (dbAcl.getAclType() == ACLType.SHARED)
{
// check unused
Long defining = dbAcl.getInheritsFrom();
if (aclCrudDAO.getAcl(defining) == null)
{
if (! isAVMNode)
{
// ADM
if (getADMNodesByAcl(aclId, 1).size() == 0)
{
// delete acl members & acl
aclCrudDAO.deleteAclMembersByAcl(aclId);
aclCrudDAO.deleteAcl(aclId);
}
}
else
{
// TODO: AVM
}
}
}
}
/**
* {@inheritDoc}
*/
@Override
public List<AclChange> deleteAccessControlList(final Long id)
{
if (logger.isDebugEnabled())
{
// debug only
int maxForDebug = 11;
List<Long> nodeIds = getADMNodesByAcl(id, maxForDebug);
for (Long nodeId : nodeIds)
{
logger.debug("deleteAccessControlList: Found nodeId=" + nodeId + ", aclId=" + id);
}
}
List<AclChange> acls = new ArrayList<AclChange>();
final AclUpdateEntity acl = aclCrudDAO.getAclForUpdate(id);
if (!acl.isLatest())
{
throw new UnsupportedOperationException("Old ACL versions can not be updated");
}
if (acl.getAclType() == ACLType.SHARED)
{
throw new UnsupportedOperationException("Delete is not supported for shared acls - they are deleted with the defining acl");
}
if ((acl.getAclType() == ACLType.DEFINING) || (acl.getAclType() == ACLType.LAYERED))
{
if ((acl.getInheritedAcl() != null) && (acl.getInheritedAcl() != -1))
{
final Acl inherited = aclCrudDAO.getAcl(acl.getInheritedAcl());
// Will remove from the cache
getWritable(inherited.getId(), acl.getInheritsFrom(), null, null, null, true, acls, WriteMode.REMOVE_INHERITED);
Acl unusedInherited = null;
for (AclChange change : acls)
{
if (change.getBefore()!= null && change.getBefore().equals(inherited.getId()))
{
unusedInherited = aclCrudDAO.getAcl(change.getAfter());
}
}
final Long newId = unusedInherited.getId();
List<Long> inheritors = aclCrudDAO.getAclsThatInheritFromAcl(newId);
for (Long nextId : inheritors)
{
// Will remove from the cache
getWritable(nextId, acl.getInheritsFrom(), null, null, acl.getInheritsFrom(), true, acls, WriteMode.REMOVE_INHERITED);
}
// delete acl members
aclCrudDAO.deleteAclMembersByAcl(newId);
// delete 'unusedInherited' acl
aclCrudDAO.deleteAcl(unusedInherited.getId());
if (inherited.isVersioned())
{
AclUpdateEntity inheritedForUpdate = aclCrudDAO.getAclForUpdate(inherited.getId());
if (inheritedForUpdate != null)
{
inheritedForUpdate.setLatest(Boolean.FALSE);
aclCrudDAO.updateAcl(inheritedForUpdate);
}
}
else
{
// delete 'inherited' acl
aclCrudDAO.deleteAcl(inherited.getId());
}
}
}
else
{
List<Long> inheritors = aclCrudDAO.getAclsThatInheritFromAcl(id);
for (Long nextId : inheritors)
{
// Will remove from the cache
getWritable(nextId, acl.getInheritsFrom(), null, null, null, true, acls, WriteMode.REMOVE_INHERITED);
}
}
// delete
if (acl.isVersioned())
{
acl.setLatest(Boolean.FALSE);
acl.setAclChangeSetId(getCurrentChangeSetId());
aclCrudDAO.updateAcl(acl);
}
else
{
// delete acl members & acl
aclCrudDAO.deleteAclMembersByAcl(id);
aclCrudDAO.deleteAcl(acl.getId());
}
acls.add(new AclChangeImpl(id, null, acl.getAclType(), null));
return acls;
}
/**
* {@inheritDoc}
*/
@Override
public List<AclChange> deleteLocalAccessControlEntries(Long id)
{
List<AclChange> changes = new ArrayList<AclChange>();
SimpleAccessControlEntry pattern = new SimpleAccessControlEntry();
pattern.setPosition(Integer.valueOf(0));
// Will remove from the cache
getWritable(id, null, Collections.singletonList(pattern), null, null, true, changes, WriteMode.COPY_UPDATE_AND_INHERIT);
return changes;
}
/**
* {@inheritDoc}
*/
@Override
public List<AclChange> deleteInheritedAccessControlEntries(Long id)
{
List<AclChange> changes = new ArrayList<AclChange>();
SimpleAccessControlEntry pattern = new SimpleAccessControlEntry();
pattern.setPosition(Integer.valueOf(-1));
// Will remove from the cache
getWritable(id, null, Collections.singletonList(pattern), null, null, true, changes, WriteMode.COPY_UPDATE_AND_INHERIT);
return changes;
}
/**
* {@inheritDoc}
*/
@Override
public List<AclChange> deleteAccessControlEntries(Long id, AccessControlEntry pattern)
{
List<AclChange> changes = new ArrayList<AclChange>();
// Will remove from the cache
getWritable(id, null, Collections.singletonList(pattern), null, null, true, changes, WriteMode.COPY_UPDATE_AND_INHERIT);
return changes;
}
/**
* {@inheritDoc}
*/
@Override
public Acl getAcl(Long id)
{
return aclCrudDAO.getAcl(id);
}
/**
* {@inheritDoc}
*/
@Override
public AccessControlListProperties getAccessControlListProperties(Long id)
{
ParameterCheck.mandatory("id", id); // Prevent unboxing failures
return aclCrudDAO.getAcl(id);
}
@Override
public void setCheckAclConsistency()
{
aclCrudDAO.setCheckAclConsistency();
}
/**
* {@inheritDoc}
*/
@Override
public AccessControlList getAccessControlList(Long id)
{
// 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<Map<String, Object>> results = aclCrudDAO.getAcesAndAuthoritiesByAcl(id);
List<AccessControlEntry> entries = new ArrayList<AccessControlEntry>(results.size());
for (Map<String, Object> result : results)
// for (AclMemberEntity member : members)
{
Boolean aceIsAllowed = (Boolean) result.get("allowed");
Integer aceType = (Integer) result.get("applies");
String authority = (String) result.get("authority");
Long permissionId = (Long) result.get("permissionId");
Integer position = (Integer) result.get("pos");
//Long result_aclmemId = (Long) result.get("aclmemId"); // not used here
SimpleAccessControlEntry sacEntry = new SimpleAccessControlEntry();
sacEntry.setAccessStatus(aceIsAllowed ? AccessStatus.ALLOWED : AccessStatus.DENIED);
sacEntry.setAceType(ACEType.getACETypeFromId(aceType));
sacEntry.setAuthority(authority);
// if (entry.getContext() != null)
// {
// SimpleAccessControlEntryContext context = new SimpleAccessControlEntryContext();
// context.setClassContext(entry.getContext().getClassContext());
// context.setKVPContext(entry.getContext().getKvpContext());
// context.setPropertyContext(entry.getContext().getPropertyContext());
// sacEntry.setContext(context);
// }
Permission perm = aclCrudDAO.getPermission(permissionId);
QName permTypeQName = qnameDAO.getQName(perm.getTypeQNameId()).getSecond(); // Has an ID so must exist
SimplePermissionReference permissionRefernce = SimplePermissionReference.getPermissionReference(permTypeQName, perm.getName());
sacEntry.setPermission(permissionRefernce);
sacEntry.setPosition(position);
entries.add(sacEntry);
}
Collections.sort(entries);
acl.setEntries(entries);
// Cache it for next time
aclCache.put((Serializable)properties, acl);
return acl;
}
/**
* {@inheritDoc}
*/
@Override
public Long getInheritedAccessControlList(Long id)
{
AclUpdateEntity acl = aclCrudDAO.getAclForUpdate(id);
if (acl.getAclType() == ACLType.OLD)
{
return null;
}
if ((acl.getInheritedAcl() != null) && (acl.getInheritedAcl() != -1))
{
return acl.getInheritedAcl();
}
Long inheritedAclId = null;
if ((acl.getAclType() == ACLType.DEFINING) || (acl.getAclType() == ACLType.LAYERED))
{
List<AclChange> changes = new ArrayList<AclChange>();
// created shared acl
SimpleAccessControlListProperties properties = new SimpleAccessControlListProperties();
properties.setAclType(ACLType.SHARED);
properties.setInherits(Boolean.TRUE);
properties.setVersioned(acl.isVersioned());
Long sharedId = createAccessControlList(properties, null, null).getId();
getWritable(sharedId, id, null, null, id, true, changes, WriteMode.ADD_INHERITED);
acl.setInheritedAcl(sharedId);
inheritedAclId = sharedId;
}
else
{
acl.setInheritedAcl(acl.getId());
inheritedAclId = acl.getId();
}
// Does not cause the change set to change
//acl.setAclChangeSetId(getCurrentChangeSetId());
aclCrudDAO.updateAcl(acl);
return inheritedAclId;
}
/**
* {@inheritDoc}
*/
@Override
public List<AclChange> mergeInheritedAccessControlList(Long inherited, Long target)
{
// TODO: For now we do a replace - we could do an insert if both inherit from the same acl
List<AclChange> changes = new ArrayList<AclChange>();
Acl targetAcl = aclCrudDAO.getAcl(target);
Acl inheritedAcl = null;
if (inherited != null)
{
inheritedAcl = aclCrudDAO.getAcl(inherited);
}
else
{
// Assume we are just resetting it to inherit as before
if (targetAcl.getInheritsFrom() != null)
{
inheritedAcl = aclCrudDAO.getAcl(targetAcl.getInheritsFrom());
if (inheritedAcl == null)
{
// TODO: Try previous versions
throw new IllegalStateException("No old inheritance definition to use");
}
else
{
// find the latest version of the acl
if (!inheritedAcl.isLatest())
{
final String searchAclId = inheritedAcl.getAclId();
Long actualInheritor = (Long)aclCrudDAO.getLatestAclByGuid(searchAclId);
inheritedAcl = aclCrudDAO.getAcl(actualInheritor);
if (inheritedAcl == null)
{
// TODO: Try previous versions
throw new IllegalStateException("No ACL found");
}
}
}
}
else
{
// There is no inheritance to set
return changes;
}
}
// recursion test
// if inherited already inherits from the target
Acl test = inheritedAcl;
while (test != null)
{
if (test.getId()!= null && test.getId().equals(target))
{
throw new IllegalStateException("Cyclical ACL detected");
}
Long parent = test.getInheritsFrom();
if ((parent == null) || (parent == -1l))
{
test = null;
}
else
{
test = aclCrudDAO.getAcl(test.getInheritsFrom());
}
}
if ((targetAcl.getAclType() != ACLType.DEFINING) && (targetAcl.getAclType() != ACLType.LAYERED))
{
throw new IllegalArgumentException("Only defining ACLs can have their inheritance set");
}
if (!targetAcl.getInherits())
{
return changes;
}
Long actualInheritedId = inheritedAcl.getId();
if ((inheritedAcl.getAclType() == ACLType.DEFINING) || (inheritedAcl.getAclType() == ACLType.LAYERED))
{
actualInheritedId = getInheritedAccessControlList(actualInheritedId);
}
// Will remove from the cache
getWritable(target, actualInheritedId, null, null, actualInheritedId, true, changes, WriteMode.CHANGE_INHERITED);
return changes;
}
/**
* {@inheritDoc}
*/
@Override
public List<AclChange> setAccessControlEntry(final Long id, final AccessControlEntry ace)
{
Acl target = aclCrudDAO.getAcl(id);
if (target.getAclType() == ACLType.SHARED)
{
throw new IllegalArgumentException("Shared ACLs are immutable");
}
List<AclChange> changes = new ArrayList<AclChange>();
if ((ace.getPosition() != null) && (ace.getPosition() != 0))
{
throw new IllegalArgumentException("Invalid position");
}
// Find authority
Authority authority = aclCrudDAO.getOrCreateAuthority(ace.getAuthority());
Permission permission = aclCrudDAO.getOrCreatePermission(ace.getPermission());
// Find context
if (ace.getContext() != null)
{
throw new UnsupportedOperationException();
}
// Find ACE
Ace entry = aclCrudDAO.getOrCreateAce(permission, authority, ace.getAceType(), ace.getAccessStatus());
// Wire up
// COW and remove any existing matches
SimpleAccessControlEntry exclude = new SimpleAccessControlEntry();
// match any access status
exclude.setAceType(ace.getAceType());
exclude.setAuthority(ace.getAuthority());
exclude.setPermission(ace.getPermission());
exclude.setPosition(0);
List<Ace> toAdd = new ArrayList<Ace>(1);
toAdd.add(entry);
// Will remove from the cache
getWritable(id, null, Collections.singletonList(exclude), toAdd, null, true, changes, WriteMode.COPY_UPDATE_AND_INHERIT);
return changes;
}
/**
* {@inheritDoc}
*/
@Override
public List<AclChange> enableInheritance(Long id, Long parent)
{
List<AclChange> changes = new ArrayList<AclChange>();
AclUpdateEntity acl = aclCrudDAO.getAclForUpdate(id);
switch (acl.getAclType())
{
case FIXED:
case GLOBAL:
throw new IllegalArgumentException("Fixed and global permissions can not inherit");
case OLD:
acl.setInherits(Boolean.TRUE);
acl.setAclChangeSetId(getCurrentChangeSetId());
aclCrudDAO.updateAcl(acl);
changes.add(new AclChangeImpl(id, id, acl.getAclType(), acl.getAclType()));
return changes;
case SHARED:
// TODO support a list of children and casacade if given
throw new IllegalArgumentException(
"Shared acls should be replace by creating a definig ACL, wiring it up for inhertitance, and then applying inheritance to any children. It can not be done by magic ");
case DEFINING:
case LAYERED:
default:
if (!acl.getInherits())
{
// Will remove from the cache
getWritable(id, null, null, null, null, false, changes, WriteMode.COPY_ONLY);
acl = aclCrudDAO.getAclForUpdate(changes.get(0).getAfter());
acl.setInherits(Boolean.TRUE);
acl.setAclChangeSetId(getCurrentChangeSetId());
aclCrudDAO.updateAcl(acl);
}
else
{
// Will remove from the cache
getWritable(id, null, null, null, null, false, changes, WriteMode.COPY_ONLY);
}
List<AclChange> merged = mergeInheritedAccessControlList(parent, changes.get(0).getAfter());
changes.addAll(merged);
return changes;
}
}
/**
* {@inheritDoc}
*/
@Override
public List<AclChange> disableInheritance(Long id, boolean setInheritedOnAcl)
{
AclUpdateEntity acl = aclCrudDAO.getAclForUpdate(id);
List<AclChange> changes = new ArrayList<AclChange>(1);
switch (acl.getAclType())
{
case FIXED:
case GLOBAL:
return Collections.<AclChange> singletonList(new AclChangeImpl(id, id, acl.getAclType(), acl.getAclType()));
case OLD:
acl.setInherits(Boolean.FALSE);
acl.setAclChangeSetId(getCurrentChangeSetId());
aclCrudDAO.updateAcl(acl);
changes.add(new AclChangeImpl(id, id, acl.getAclType(), acl.getAclType()));
return changes;
case SHARED:
// TODO support a list of children and casacade if given
throw new IllegalArgumentException("Shared ACL must inherit");
case DEFINING:
case LAYERED:
default:
return disableInheritanceImpl(id, setInheritedOnAcl, acl);
}
}
private Long getCopy(Long toCopy, Long toInheritFrom, ACLCopyMode mode)
{
AclUpdateEntity aclToCopy;
Long inheritedId;
Acl aclToInheritFrom;
switch (mode)
{
case INHERIT:
if (toCopy.equals(toInheritFrom))
{
return getInheritedAccessControlList(toCopy);
}
else
{
throw new UnsupportedOperationException();
}
case COW:
aclToCopy = aclCrudDAO.getAclForUpdate(toCopy);
aclToCopy.setRequiresVersion(true);
aclToCopy.setAclChangeSetId(getCurrentChangeSetId());
aclCrudDAO.updateAcl(aclToCopy);
inheritedId = getInheritedAccessControlList(toCopy);
if ((inheritedId != null) && (!inheritedId.equals(toCopy)))
{
AclUpdateEntity inheritedAcl = aclCrudDAO.getAclForUpdate(inheritedId);
inheritedAcl.setRequiresVersion(true);
inheritedAcl.setAclChangeSetId(getCurrentChangeSetId());
aclCrudDAO.updateAcl(inheritedAcl);
}
return toCopy;
case REDIRECT:
if ((toInheritFrom != null) && (toInheritFrom.equals(toCopy)))
{
return getInheritedAccessControlList(toInheritFrom);
}
aclToCopy = aclCrudDAO.getAclForUpdate(toCopy);
aclToInheritFrom = null;
if (toInheritFrom != null)
{
aclToInheritFrom = aclCrudDAO.getAcl(toInheritFrom);
}
switch (aclToCopy.getAclType())
{
case DEFINING:
// This is not called on the redirecting node as only LAYERED change permissions when redirected
// So this needs to make a copy in the same way layered does
case LAYERED:
if (toInheritFrom == null)
{
return toCopy;
}
// manages cache clearing beneath
List<AclChange> changes = mergeInheritedAccessControlList(toInheritFrom, toCopy);
for (AclChange change : changes)
{
if (change.getBefore().equals(toCopy))
{
return change.getAfter();
}
}
throw new UnsupportedOperationException();
case SHARED:
if (aclToInheritFrom != null)
{
return getInheritedAccessControlList(toInheritFrom);
}
else
{
throw new UnsupportedOperationException();
}
case FIXED:
case GLOBAL:
case OLD:
return toCopy;
default:
throw new UnsupportedOperationException();
}
case COPY:
aclToCopy = aclCrudDAO.getAclForUpdate(toCopy);
aclToInheritFrom = null;
if (toInheritFrom != null)
{
aclToInheritFrom = aclCrudDAO.getAcl(toInheritFrom);
}
switch (aclToCopy.getAclType())
{
case DEFINING:
SimpleAccessControlListProperties properties = new SimpleAccessControlListProperties();
properties.setAclType(ACLType.DEFINING);
properties.setInherits(aclToCopy.getInherits());
properties.setVersioned(true);
Long id = createAccessControlList(properties).getId();
AccessControlList indirectAcl = getAccessControlList(toCopy);
for (AccessControlEntry entry : indirectAcl.getEntries())
{
if (entry.getPosition() == 0)
{
setAccessControlEntry(id, entry);
}
}
if (aclToInheritFrom != null)
{
mergeInheritedAccessControlList(toInheritFrom, id);
}
return id;
case SHARED:
if (aclToInheritFrom != null)
{
return getInheritedAccessControlList(toInheritFrom);
}
else
{
return null;
}
case FIXED:
case GLOBAL:
case LAYERED:
case OLD:
return toCopy;
default:
throw new UnsupportedOperationException();
}
default:
throw new UnsupportedOperationException();
}
}
/**
* {@inheritDoc}
*/
@Override
public Acl getAclCopy(Long toCopy, Long toInheritFrom, ACLCopyMode mode)
{
return getAclEntityCopy(toCopy, toInheritFrom, mode);
}
private Acl getAclEntityCopy(Long toCopy, Long toInheritFrom, ACLCopyMode mode)
{
Long id = getCopy(toCopy, toInheritFrom, mode);
if (id == null)
{
return null;
}
return aclCrudDAO.getAcl(id);
}
/**
* {@inheritDoc}
*/
@Override
public List<Long> getAVMNodesByAcl(long aclEntityId, int maxResults)
{
return aclCrudDAO.getAVMNodesByAcl(aclEntityId, maxResults);
}
/**
* {@inheritDoc}
*/
@Override
public List<Long> getADMNodesByAcl(long aclEntityId, int maxResults)
{
return aclCrudDAO.getADMNodesByAcl(aclEntityId, maxResults);
}
/**
* {@inheritDoc}
*/
@Override
public Acl createLayeredAcl(Long indirectedAcl)
{
SimpleAccessControlListProperties properties = new SimpleAccessControlListProperties();
properties.setAclType(ACLType.LAYERED);
Acl acl = createAccessControlList(properties);
long id = acl.getId();
if (indirectedAcl != null)
{
mergeInheritedAccessControlList(indirectedAcl, id);
}
return acl;
}
private List<AclChange> disableInheritanceImpl(Long id, boolean setInheritedOnAcl, AclEntity aclIn)
{
List<AclChange> changes = new ArrayList<AclChange>();
if (!aclIn.getInherits())
{
return Collections.<AclChange> emptyList();
}
// Manages caching
getWritable(id, null, null, null, null, false, changes, WriteMode.COPY_ONLY);
AclUpdateEntity acl = aclCrudDAO.getAclForUpdate(changes.get(0).getAfter());
final Long inheritsFrom = acl.getInheritsFrom();
acl.setInherits(Boolean.FALSE);
acl.setAclChangeSetId(getCurrentChangeSetId());
aclCrudDAO.updateAcl(acl);
// Keep inherits from so we can reinstate if required
// acl.setInheritsFrom(-1l);
// Manages caching
getWritable(acl.getId(), null, null, null, null, true, changes, WriteMode.TRUNCATE_INHERITED);
// set Inherited - TODO: UNTESTED
if ((inheritsFrom != null) && (inheritsFrom != -1) && setInheritedOnAcl)
{
// get aces for acl (via acl member)
List<AclMember> members = aclCrudDAO.getAclMembersByAcl(inheritsFrom);
for (AclMember member : members)
{
// TODO optimise
Ace ace = aclCrudDAO.getAce(member.getAceId());
Authority authority = aclCrudDAO.getAuthority(ace.getAuthorityId());
SimpleAccessControlEntry entry = new SimpleAccessControlEntry();
entry.setAccessStatus(ace.isAllowed() ? AccessStatus.ALLOWED : AccessStatus.DENIED);
entry.setAceType(ace.getAceType());
entry.setAuthority(authority.getAuthority());
/* NOTE: currently unused - intended for possible future enhancement
if (ace.getContextId() != null)
{
AceContext aceContext = aclCrudDAO.getAceContext(ace.getContextId());
SimpleAccessControlEntryContext context = new SimpleAccessControlEntryContext();
context.setClassContext(aceContext.getClassContext());
context.setKVPContext(aceContext.getKvpContext());
context.setPropertyContext(aceContext.getPropertyContext());
entry.setContext(context);
}
*/
Permission perm = aclCrudDAO.getPermission(ace.getPermissionId());
QName permTypeQName = qnameDAO.getQName(perm.getTypeQNameId()).getSecond(); // Has an ID so must exist
SimplePermissionReference permissionRefernce = SimplePermissionReference.getPermissionReference(permTypeQName, perm.getName());
entry.setPermission(permissionRefernce);
entry.setPosition(Integer.valueOf(0));
setAccessControlEntry(id, entry);
}
}
return changes;
}
private static final String RESOURCE_KEY_ACL_CHANGE_SET_ID = "acl.change.set.id";
private UpdateChangeSetListener updateChangeSetListener = new UpdateChangeSetListener();
/**
* Wrapper to update the current changeset to get the change time correct
*
* @author Derek Hulley
* @since 4.0
*/
private class UpdateChangeSetListener extends TransactionListenerAdapter
{
@Override
public void beforeCommit(boolean readOnly)
{
if (readOnly)
{
return;
}
Long changeSetId = (Long) AlfrescoTransactionSupport.getResource(RESOURCE_KEY_ACL_CHANGE_SET_ID);
if (changeSetId == null)
{
// There has not been a change
return;
}
// Update it
long commitTimeMs = System.currentTimeMillis();
aclCrudDAO.updateAclChangeSet(changeSetId, commitTimeMs);
}
}
/**
* Support to get the current ACL change set and bind this to the transaction. So we only make one new version of an
* ACL per change set. If something is in the current change set we can update it.
*/
private long getCurrentChangeSetId()
{
Long changeSetId = (Long) AlfrescoTransactionSupport.getResource(RESOURCE_KEY_ACL_CHANGE_SET_ID);
if (changeSetId == null)
{
changeSetId = aclCrudDAO.createAclChangeSet();
// bind the ID and the listener
AlfrescoTransactionSupport.bindResource(RESOURCE_KEY_ACL_CHANGE_SET_ID, changeSetId);
AlfrescoTransactionSupport.bindListener(updateChangeSetListener);
if (logger.isDebugEnabled())
{
logger.debug("New change set = " + changeSetId);
}
}
return changeSetId;
}
private static class AcePatternMatcher
{
private List<? extends AccessControlEntry> patterns;
AcePatternMatcher(List<? extends AccessControlEntry> patterns)
{
this.patterns = patterns;
}
boolean matches(AclCrudDAO aclCrudDAO, Map<String, Object> result, int position)
{
if (patterns == null)
{
return true;
}
for (AccessControlEntry pattern : patterns)
{
if (checkPattern(aclCrudDAO, result, position, pattern))
{
return true;
}
}
return false;
}
private boolean checkPattern(AclCrudDAO aclCrudDAO, Map<String, Object> result, int position, AccessControlEntry pattern)
{
Boolean result_aceIsAllowed = (Boolean) result.get("allowed");
Integer result_aceType = (Integer) result.get("applies");
String result_authority = (String) result.get("authority");
Long result_permissionId = (Long) result.get("permissionId");
Integer result_position = (Integer) result.get("pos");
//Long result_aclmemId = (Long) result.get("aclmemId"); // not used
if (pattern.getAccessStatus() != null)
{
if (pattern.getAccessStatus() != (result_aceIsAllowed ? AccessStatus.ALLOWED : AccessStatus.DENIED))
{
return false;
}
}
if (pattern.getAceType() != null)
{
if (pattern.getAceType() != ACEType.getACETypeFromId(result_aceType))
{
return false;
}
}
if (pattern.getAuthority() != null)
{
if ((pattern.getAuthorityType() != AuthorityType.WILDCARD) && !pattern.getAuthority().equals(result_authority))
{
return false;
}
}
if (pattern.getContext() != null)
{
throw new IllegalArgumentException("Context not yet supported");
}
if (pattern.getPermission() != null)
{
Long permId = aclCrudDAO.getPermission(pattern.getPermission()).getId();
if (!permId.equals(result_permissionId))
{
return false;
}
}
if (pattern.getPosition() != null)
{
if (pattern.getPosition().intValue() >= 0)
{
if (result_position != position)
{
return false;
}
}
else if (pattern.getPosition().intValue() == -1)
{
if (result_position <= position)
{
return false;
}
}
}
return true;
}
}
static class AclChangeImpl implements AclChange
{
private Long before;
private Long after;
private ACLType typeBefore;
private ACLType typeAfter;
public AclChangeImpl(Long before, Long after, ACLType typeBefore, ACLType typeAfter)
{
this.before = before;
this.after = after;
this.typeAfter = typeAfter;
this.typeBefore = typeBefore;
}
public Long getAfter()
{
return after;
}
public Long getBefore()
{
return before;
}
/**
* @param after
*/
public void setAfter(Long after)
{
this.after = after;
}
/**
* @param before
*/
public void setBefore(Long before)
{
this.before = before;
}
public ACLType getTypeAfter()
{
return typeAfter;
}
/**
* @param typeAfter
*/
public void setTypeAfter(ACLType typeAfter)
{
this.typeAfter = typeAfter;
}
public ACLType getTypeBefore()
{
return typeBefore;
}
/**
* @param typeBefore
*/
public void setTypeBefore(ACLType typeBefore)
{
this.typeBefore = typeBefore;
}
@Override
public String toString()
{
StringBuilder builder = new StringBuilder();
builder.append("(").append(getBefore()).append(",").append(getTypeBefore()).append(")");
builder.append(" - > ");
builder.append("(").append(getAfter()).append(",").append(getTypeAfter()).append(")");
return builder.toString();
}
}
/**
* {@inheritDoc}
*/
@Override
public void renameAuthority(String before, String after)
{
aclCrudDAO.renameAuthority(before, after);
aclCache.clear();
}
/**
* {@inheritDoc}
*/
@Override
public void fixSharedAcl(Long shared, Long defining)
{
if (defining == null)
{
throw new IllegalArgumentException("Null defining acl");
}
if (shared == null)
{
throw new IllegalArgumentException("Null shared acl");
}
List<AclChange> changes = new ArrayList<AclChange>();
getWritable(shared, defining, null, null, defining, true, changes, WriteMode.CHANGE_INHERITED);
}
/* (non-Javadoc)
* @see org.alfresco.repo.domain.permissions.AclDAO#getMaxChangeSetCommitTime()
*/
@Override
public Long getMaxChangeSetCommitTime()
{
return aclCrudDAO.getMaxChangeSetCommitTime();
}
/* (non-Javadoc)
* @see org.alfresco.repo.domain.permissions.AclDAO#getMaxChangeSetIdByCommitTime(long)
*/
@Override
public Long getMaxChangeSetIdByCommitTime(long maxCommitTime)
{
return aclCrudDAO.getMaxChangeSetIdByCommitTime(maxCommitTime);
}
}