Dave Ward 811519ae48 Merged V4.0-BUG-FIX to HEAD
33116: Allow multiple deferred requests per oplock break, next level of fix for ALF-11935.
   33136: Fix for ALF-12200: "Content type is not recognized on creating document from source"
   33137: Activities feed generator: change info log messages to debug log messages
   33139: ALF-12262	View in Source Repository works incorrect if the folder was created on target side at first
   33141: Fix for ALF-12178 "Bulk import - status page shows broken link (Initiate another in-place import)"
   33144: Fixes ALF-11119: The 2 tranformations didn't work - Segmentation fault in ffmpeg - it looks like a bug with ffmpeg and it was decided that compile/bug fixing ffmpeg is not a priority at the moment.  The 2 entries have been commented out.  If they are required, the user should consult the ffmpeg documentation for the correct version & o/s at the time.
   33146: ALF-11345 Patch from Pavel Yurkevich to fix another VTI/SPP problem with site names that start with Alfresco
   33147: FTP implemented set modification date/time command (MFMT). ALF-12105.
   33148: ALF-12063 Pull some of the VTI list type definitions out to a common base class
   33150: Merged BRANCHES/DEV/mward/schemacomp to BRANCHES/DEV/V4.0-BUG-FIX:
      33076: ALF-12285: Allow dumping of schema to XML via JMX
   33151: Fix problems with FTP and UTF-8. JLAN-81.
   When using the Java6 Normalizer use the NFC form.
   33158: Fix NFS server swallows exceptions. ALF-11667.
   Startup exception details are now saved.
   33160: Improve the Vti/SPP exception message for the case of the Vti port being already in use
   33161: ALF-12063 Additional SPP/Vti list info required for Mac Office 2011 support
   33162: Fixes: ALF-10322: Edit Dialogue gets out of sync if event moved using FullCalendar (extends FullCalendar to provide a callback after an event changes, which enables us to keep our event object up to date).
   33163: Fixes: ALF-10248: Grey Placeholder image for unauthorised channels has now been replaced with yellow one.
   33164: Fixes: ALF-11562; Refactors My Calendar dashlet to use timezone aware ISO8601 dates from updated Calendar API & removes obsolete properties from userevents calendar API
   33165: Fixes: ALF-10645; i18n label doesn't appear in property bundle.
   33167: Fix for ALF-11970
   33168: Fix for ALF-10565 "Category manager in admin console needs query not search"
     - i18n'd the strings
   33178: Publishing: Fixes: ALF-11552; Inline edit icon alignment issue fixed
   33183: Minor fix to exception string in extendBuffer().
   33194: ALF-10545: NodeServicePolicies#onUpdateNodePolicy not adequate for NodeService#setType
      * Added beforeSetNodeType and onSetNodeType policies
      * Both callbacks have old and new types as parameters
   33204: Fixes: ALF-11230, publishing tracking link opens in repository, not Doc Lib. Also fixes a couple of other minor bugs:
      - balloon pop up didn't appear when published from Doc Details page
      - tracking link appears black (on black) when hovered over.
      - adds defensive code to prevent an error if the expected element for the notification balloon isn't there.
   33212: Merged BRANCHES/DEV/mward/schemacomp to BRANCHES/DEV/V4.0-BUG-FIX:
      33211: ALF-12384: Failed schema dump can cause failure of repository start up
   33217: Merged V3.4-BUG-FIX to V4.0-BUG-FIX
      31840: Fix for ALF-10282 - Web Browser freezes with large xml files Web form transformation
      31987: Proper fix for ALF-11489: 'patch.sitesSpacePermissions' failed on upgrade 2.2.8 -> 3.4.6
         - Just handle missing defined ACLs   
      32341: Fix for ALF-9883 - WCM Forms: Changing 'abstract' type carries previously-added elements
      32911: Add a (currently disabled) unit test for ALF-10466 - The HTML to Text transformer (not Tika based) should take account of the content encoding
      32912: Merged BRANCHES/DEV/BELARUS/V3.4-BUG-FIX-2011_10_13 to BRANCHES/DEV/V3.4-BUG-FIX with changes + unit testing:
         31742: ALF-10466 - The HTML to Text converter needs to take account of the Encoding set on the Content Property, to be able to correctly index MBCS text in wiki pages (and others)   
      32946: ALF-12161: Merged PATCHES/V3.4.5 to V3.4-BUG-FIX
         32921: Merged DEV/TEMPORARY to PATCHES/V3.4.5
            32913: ALF-11440: Content Manager unable to edit content from another user sandbox
               In order to allow a Content Manager to edit a locked document in other user's sandbox, it is needed to modify the AVMLockingAwareService.grabLock().
               The 'lockState' variable could be set to 'LOCK_OWNER', if a user is ContentManager to bypass the check.   
      32964: Fixes ALF-11054: Sharepoint - Wrong sorting by date
        - incorporated patch from investigation team
      32967: Merged BRANCHES/DEV/BELARUS/V3.4-BUG-FIX-2011_10_13 to BRANCHES/DEV/V3.4-BUG-FIX:
         31828: Fixes ALF-10720: Webform performance improvement
               Minimize database usage by adding new variable to FormWrapper that holds a form's name.   
      32969: Fixes ALF-10471: Cannot correctly remove users from email notification rule list
      32980: Merged DEV/TEMPORARY to V3.4-BUG-FIX
         32961: ALF-12132: Set "common-placeholder-configurer" as parent for "lotusWSPlaceholderConfigurer" bean.    
      32996: ALF-12184: SchemaBootstrap must use same assumptions as PatchServiceImpl when deciding whether an alternative patch succeeded
      - Fixes regression introduced by r31972 / ALF-11489
      33068: Added suggested fix to commit any current transactions in the NFS file expiry thread. ALF-11827.
      33077: ALF-10142: Allow TinyMCE to accept <meta> element when editing HTML files inline in Share.
      33094: Fix for ACT #15024-37148 (no JIRA yet)
       - issue where in a load balanced Share environment (multiple web-tiers behind a reverse proxy) the modification to the template layout selection for a site or user dashboard would not be reflected in all servers.
      33118: ALF-12278: Prevent the copying over of headers specific to a POST request on to the touch GET request   
      33138: Upgraded SpringSurf to 1.0.0 rev 968
      33140: Added missing json-simple jar to 3rd party eclipse classpath, which bizarrely is used to generate the JUnit cmd line unit test classpath, no really.
      33145: Fix for native FTP timestamps returned in GMT timezone format. ALF-11986.
      33175: ALF-12366: Cope with read committed DB behaviour in AbstractReindexComponent.reindexTransaction()
      33179: ALF-12344 CLONE - Copyright notice shows Alfresco Software, Inc. © 2005-2011 All rights reserved.... should now be to 2012 as that is when we will release 3.4.7
          - Being done in 3.4.8 not 3.4.7
      33190: Latest SpringSurf libs:
       - much improved handling of multiple connections and connection reuse in RemoteClient
       - improves connection reuse generally, but also much more stable under load balancing condition with multiple Share web-tiers behind a reverse proxy
      33193: ALF-12344 CLONE - Copyright notice shows Alfresco Software, Inc. © 2005-2011 All rights reserved.... should now be to 2012 as that is when we will release 3.4.7
          - Found a few more having followed previous date changes and searches
      33203: Fix for HttpClient issue: Error status 500 Unbuffered entity enclosing request can not be repeated.
      33206: Fix to FormUIGet - no need to manually patch up the JSON request since rev 33138 (SpringSurf 1.0.0)
   33218: Fix for ALF-11868 "CMIS: removeAcl() function doesn't work via atompub."
   33220: Merged PATCHES/V3.4.6 to V4.0-BUG-FIX
      32405: Merged V3.4.6 (3.4.6.1) to V3.4.1 (3.4.1.24)
         32404: ALF-11727 CLONE - Pending Invite Search doesn't return anything if there's more than 1000 pending invites across all sites.
            Removed read only transaction from invites.get.desc.xml as it broke InviteServiceTest testRejectInvite
         32397: ALF-11727 CLONE - Pending Invite Search doesn't return anything if there's more than 1000 pending invites across all sites.
            Return first 200 invitations (similar to 4.0 paging)
            Transaction used by the invites.get is now read only so does not force a flush of caches.   
      32503: Merged PATCHES/V3.4.6 to PATCHES/V3.4.1
         32501: ALF-11727: Reinstated read-only transaction around invites.get and prevented it from trying to lazily create persons from rejected invites that had previously been deleted by InviteHelper.cleanUpStaleInviteeResources!   
      32650: ALF-11872: When there are a lot of pending invites, deletion of a site causes high and prolonged CPU activity and can take a long time to complete
         Put back in cut off at 200 invites (removed in last merge) for UI, unless we know that we need all of them internally
      32775: ALF-11872 When there are a lot of pending invites, deletion of a site causes high and prolonged CPU activity and can take a long time to complete
         Even more changes:
         - reduce number of queries required to list pending invites to a site (uses moderated and nominated caches and only looks up IDs if possible)
         - change hibernate cache and flush modes (to avoid cache and the related slow flush), for queries and cancel of workflows
         - modified js which was making a query for each person in order to work out if they were already in a pending invites list
      32838: ALF-11872 When there are a lot of pending invites, deletion of a site causes high and prolonged CPU activity and can take a long time to complete
      - JBPMEngine now supports batch cancelWorkflows() method for canceling multiple workflows at the same time (e.g. on deleting a site)
      - Manual flushes only used at two points in the batch to minimize dirty checking overhead and yet avoid FK errors
      - Performance implications still to be checked but at least functionally correct
      - Corrected JPDL source jar
      32857: ALF-11872 When there are a lot of pending invites, deletion of a site causes high and prolonged CPU activity and can take a long time to complete
         - Search for Pending invites was slow on sites with > 0 pending invites (it was getting invites for all sites) 
      32861: ALF-11872 When there are a lot of pending invites, deletion of a site causes high and prolonged CPU activity and can take a long time to complete
      - JBPMEngine uses an abstract list to 'lazily' convert hibernate objects on demand and avoid batch loading too many objects
      32868: ALF-11872 When there are a lot of pending invites, deletion of a site causes high and prolonged CPU activity and can take a long time to complete
      - Rationalization of batch fetching at hibernate layer
      32881: ALF-11872: The saga goes on! Corrected empty list handling in InvitationServiceImpl.searchInvitation().
      32927: ALF-11872: Fix parameter validation in InvitationServiceImpl.getInvitationTasks()
      32936: ALF-11872: A site with 1200 pending invites can now be deleted without the UI timing out
      - Pending invitation workflows are cancelled in an asynchronous action
      - The asynchronous action completes about 3 minutes later, due to the massive number of individual delete statements being run by Hibernate
      - Creating the rows in the first place took 20 minutes!   
      32956: ALF-11872: Corrected filtering in InvitationServiceImpl.getInvitationTasks() to only include start tasks
      - plus recautionary sleep() in InviteServiceTest.tearDown() to ensure asynchronous invite deletions complete
      33169: ALF-12312 'org.hibernate.LazyInitializationException: could not initialize proxy - no Session' when clicking on a Pending Invite workflow task in JSF
         - Follow on from ALF-11872: Only use lazyloaded WorkflowTasks from JBPMEngine.getWorkflowTasks() when we are using the same session (currently
           only done from InvitationService). The fallback is to assume it is not the same session and return a normal list of Workflows.
   33221: Merged V3.4-BUG-FIX to V4.0-BUG-FIX (RECORD ONLY)
      30463: L10N Updates from Gloria (based on r30332): Fixes ALF-8211 and new string updates
      30473: Merged HEAD to V3.4-BUG-FIX
         30468: Fixed ALF-10280: Slow to report ® Duplicate entry ¯ in database.
                - DuplicateChildNodeNameException implements DoNotRetryException   
      30685: Merged HEAD to V3.4-BUG-FIX
         30679: Unit test for ALF-1017 - Non site content in the Sites Space
         30683: ALF-1017 Remove EVERYONE Contributor permissions from /Company Home/Sites/, to avoid misc nodes being created in there by mistake by users, and update the SiteService to runAsSystem when creating the Site node   
      30693: Merged HEAD to V3.4-BUG-FIX
         30692: Fix ALF-1017 specific test following ALF-1017 changes to permissions   
      30808: Fixes: ALF-10485 (minor text update)
      30873: Latest L10N update from Gloria (based on r30698):
         - Adds Web Quick Start translations (back port from 4.0)
         - Adds/updates new or previously missing strings
      31019: Merged HEAD to V3.4-BUG-FIX
         28974: added double-checks for associations when a potential failure is detected
         31018: Fixed ALF-9591: Integrity check: Association source multiplicity checking is incorrect
                - Drop checks for source multiplicity when no associations are pointing to a type/aspect instance   
      31045: Merged HEAD to V3.4-BUG-FIX
         31044: Performance improvements for PATH queries relating to Share dashboard dashlets and document library.
                - tweaks to generation of PATH queries - hugely improves performance when dealing with 1000's site memberships
      31160: Merged HEAD to V3.4-BUG-FIX
         31156: Various Share search related fixes as spotted by Andy:
                - increased resultset size that is used to retrieve raw results from query before Share specific results are filtered - this means sensible results are now shown from large repository wide and sorted queries, previously results would be "missing" if they dropped out of the resultset prefiltering
                - fix to Share Search component to correctly display if more than N results were found in the repository
                - fix to add default TYPE clause to generated Share search if no other TYPE is specified - this reduces masses of potentially matches results from repository wide searches that would otherwise need to be post-filtered
      31345: Merged HEAD to BRANCHES/DEV/V3.4-BUG-FIX:
         31330: Fixed WCM bulkImport's importDirectory
      31442: Merged HEAD to V3.4-BUG-FIX
         31441: Fixed ALF-11014: Content output stream close errors are absorbed silently
                - Found while testing XAMcontentStore
                - Pulled stream copy code into AbstractContentWriter
                - OutputStream closure (write-side) exception is rethrown to allow full rollback, etc   
      31444: Merged HEAD to BRANCHES/DEV/V3.4-BUG-FIX:
         31383: MLPropertyInterceptor performance improvements:
      31758: Merged V3.3 to V3.4-BUG-FIX
         31757: ALF-11279: Fixed RetryingTransactionInterceptor so that it actually behaves like an interceptor and doesn't throw away the rest of the interceptor chain!   
      31798: Merged V3.3 to V3.4-BUG-FIX
         31773: ALF-11279: Further RetryingTransactionInterceptor fixes - must do mark for rollback on propagating transactions   
      32051: ALF-7195: Merge HEAD (4.0) to V3.4-BUG-FIX (3.4.7)
         Merge was simply to take HEAD version to pick up changes made by Derek to DisableAuditableBehaviourInterceptor
         32047: ALF-8882 Edit Online: Modifier and Modified date are changed even no changes were applied
            - needed to turn off ASPECT_AUDITABLE on removeProperty which is called on unlock
            - added code to not enable this aspect early if nested calls were made (this is not done, but is safer this way)
      32088: Merging HEAD to 3.4-BUG-FIX:
      r32063: ALF-10947 Fixed issue where repeating JBPM timer was causing an infinite loop if an exception was thrown within the timer event.
      32475: ALF-11727 Improved performance of pending invites search.
      32512: Incremented version revision for 3.4.8
      32917: ALF-12133: Merged HEAD to V3.4-BUG-FIX
         32906: ALF-12068 - Zimbra desktop - corrected InternalDateAsString.   
      32923: ALF-12133: Merged HEAD to V3.4-BUG-FIX
         32918: ALF-12133 - Attempt 2 to get Imap internal date correct.   
      33021: ALF-9878: Merge V3.4.1 (3.4.1.25) to V3.4-BUG-FIX (3.4.8)
         32956: ALF-11872: Corrected filtering in InvitationServiceImpl.getInvitationTasks() to only include start tasks
         - plus recautionary sleep() in InviteServiceTest.tearDown() to ensure asynchronous invite deletions complete
         32936: ALF-11872: A site with 1200 pending invites can now be deleted without the UI timing out
         - Pending invitation workflows are cancelled in an asynchronous action
         - The asynchronous action completes about 3 minutes later, due to the massive number of individual delete statements being run by Hibernate
         - Creating the rows in the first place took 20 minutes!
         32927: ALF-11872: Fix parameter validation in InvitationServiceImpl.getInvitationTasks()
         32881: ALF-11872: The saga goes on! Corrected empty list handling in InvitationServiceImpl.searchInvitation().
         32868: ALF-11872 When there are a lot of pending invites, deletion of a site causes high and prolonged CPU activity and can take a long time to complete
         - Rationalization of batch fetching at hibernate layer
         32861: ALF-11872 When there are a lot of pending invites, deletion of a site causes high and prolonged CPU activity and can take a long time to complete
         - JBPMEngine uses an abstract list to 'lazily' convert hibernate objects on demand and avoid batch loading too many objects
         32857: ALF-11872 When there are a lot of pending invites, deletion of a site causes high and prolonged CPU activity and can take a long time to complete
            - Search for Pending invites was slow on sites with > 0 pending invites (it was getting invites for all sites) 
         32838: ALF-11872 When there are a lot of pending invites, deletion of a site causes high and prolonged CPU activity and can take a long time to complete
         - JBPMEngine now supports batch cancelWorkflows() method for canceling multiple workflows at the same time (e.g. on deleting a site)
         - Manual flushes only used at two points in the batch to minimize dirty checking overhead and yet avoid FK errors
         - Performance implications still to be checked but at least functionally correct
         - Corrected JPDL source jar
         32775: ALF-11872 When there are a lot of pending invites, deletion of a site causes high and prolonged CPU activity and can take a long time to complete
            Even more changes:
            - reduce number of queries required to list pending invites to a site (uses moderated and nominated caches and only looks up IDs if possible)
            - change hibernate cache and flush modes (to avoid cache and the related slow flush), for queries and cancel of workflows
            - modified js which was making a query for each person in order to work out if they were already in a pending invites list
         32650: ALF-11872: When there are a lot of pending invites, deletion of a site causes high and prolonged CPU activity and can take a long time to complete
            Put back in cut off at 200 invites (removed in last merge) for UI, unless we know that we need all of them internally   
      33143: Merged BRANCHES/V3.4-TEAM to BRANCHES/DEV/V3.4-BUG-FIX
         25103: ALF-6613 - SpringSurf improvements to allow easier refactoring of Document Details page
                - removed manual request level caching of remote calls responses in web-tier components - now completely automatic
         25138: Flattening of user preferences remote calls - ensures /preferences hits the RequestCachingConnector - reduces no. of remote calls by 3 for the doclib and by 4 for a site dashboard.
      33216: Merged PATCHES/V3.4.1 to V3.4-BUG-FIX (3.4.8)
         32405: Merged V3.4.6 (3.4.6.1) to V3.4.1 (3.4.1.24)
            32404: ALF-9878 / ALF-11727 CLONE - Pending Invite Search doesn't return anything if there's more than 1000 pending invites across all sites.
               Removed read only transaction from invites.get.desc.xml as it broke InviteServiceTest testRejectInvite
            32397: ALF-9878 / ALF-11727 CLONE - Pending Invite Search doesn't return anything if there's more than 1000 pending invites across all sites.
               Return first 200 invitations (similar to 4.0 paging)
               Transaction used by the invites.get is now read only so does not force a flush of caches.   
         32503: Merged PATCHES/V3.4.6 to PATCHES/V3.4.1
            32501: ALF-9878 / ALF-11727: Reinstated read-only transaction around invites.get and prevented it from trying to lazily create persons from rejected invites that had previously been deleted by InviteHelper.cleanUpStaleInviteeResources!   
         32641: ALF-12387 / ALF-11872: Merged V3.4-BUG-FIX (3.4.8) to V3.4.1 (3.4.1.25)
               32475: ALF-11727 Improved performance of pending invites search.
         	     (N Smith changes to do with only using ONE search value - faster as multiple are taken as ORs rather than ANDs)
         32650: ALF-12387 / ALF-11872: When there are a lot of pending invites, deletion of a site causes high and prolonged CPU activity and can take a long time to complete
            Put back in cut off at 200 invites (removed in last merge) for UI, unless we know that we need all of them internally
         32775: ALF-12387 / ALF-11872 When there are a lot of pending invites, deletion of a site causes high and prolonged CPU activity and can take a long time to complete
            Even more changes:
            - reduce number of queries required to list pending invites to a site (uses moderated and nominated caches and only looks up IDs if possible)
            - change hibernate cache and flush modes (to avoid cache and the related slow flush), for queries and cancel of workflows
            - modified js which was making a query for each person in order to work out if they were already in a pending invites list
         32838: ALF-12387 / ALF-11872 When there are a lot of pending invites, deletion of a site causes high and prolonged CPU activity and can take a long time to complete
         - JBPMEngine now supports batch cancelWorkflows() method for canceling multiple workflows at the same time (e.g. on deleting a site)
         - Manual flushes only used at two points in the batch to minimize dirty checking overhead and yet avoid FK errors
         - Performance implications still to be checked but at least functionally correct
         - Corrected JPDL source jar
         32857: ALF-12387 / ALF-11872 When there are a lot of pending invites, deletion of a site causes high and prolonged CPU activity and can take a long time to complete
            - Search for Pending invites was slow on sites with > 0 pending invites (it was getting invites for all sites) 
         32861: ALF-12387 / ALF-11872 When there are a lot of pending invites, deletion of a site causes high and prolonged CPU activity and can take a long time to complete
         - JBPMEngine uses an abstract list to 'lazily' convert hibernate objects on demand and avoid batch loading too many objects
         32868: ALF-12387 / ALF-11872 When there are a lot of pending invites, deletion of a site causes high and prolonged CPU activity and can take a long time to complete
         - Rationalization of batch fetching at hibernate layer
         32881: ALF-12387 / ALF-11872: The saga goes on! Corrected empty list handling in InvitationServiceImpl.searchInvitation().
         32927: ALF-12387 / ALF-11872: Fix parameter validation in InvitationServiceImpl.getInvitationTasks()
         32936: ALF-12387 / ALF-11872: A site with 1200 pending invites can now be deleted without the UI timing out
         - Pending invitation workflows are cancelled in an asynchronous action
         - The asynchronous action completes about 3 minutes later, due to the massive number of individual delete statements being run by Hibernate
         - Creating the rows in the first place took 20 minutes!   
         32956: ALF-12387 / ALF-11872: Corrected filtering in InvitationServiceImpl.getInvitationTasks() to only include start tasks
         - plus recautionary sleep() in InviteServiceTest.tearDown() to ensure asynchronous invite deletions complete
         33169: ALF-12382 / ALF-12312 'org.hibernate.LazyInitializationException: could not initialize proxy - no Session' when clicking on a Pending Invite workflow task in JSF
            - Follow on from ALF-11872: Only use lazyloaded WorkflowTasks from JBPMEngine.getWorkflowTasks() when we are using the same session (currently
              only done from InvitationService). The fallback is to assume it is not the same session and return a normal list of Workflows.   
   33224: Fix for ALF-12230 "Bootstrap re-encryption failed"
   33225: Fix for ALF-12349 Transformations need to handle failures due to server being busy.
     Change to system behaviour:
       We want to support transient failures of thumbnail creations. Primary example now is the Polymorph Transformation Server which
       can fail transformations because it is under load and wishes to decline transform requests.
       Prior to this check-in, such failures would be interpreted by Alfresco as 'real' failures and the content node would be marked
       as FailedToTransform. The transformers' state data would also be affected by the failure.
       We need to allow transformers to fail and NOT trigger any negative consequences such as above.
     Changes in code:
     New exception type ActionServiceTransientException when thrown from an ActionExecuter will NOT trigger any compensating action that has been configured.
       This allows actions to fail 'normally' or due to some transient condition with the implication that if rerun later the action may succeed.
     Additional end-state for Actions in the ActionTrackingService: ActionStatus.Declined. The ActionExecuter has declined to execute the action at this time.
     New exception type ContentServiceTransientException which means that a content operation (currently only a transformation) has failed due to a transient condition.
       CreateThumbnailActionExecuter catches this exception type and throws the ActionServiceTransientException.
     Tests at the ActionServiceImpl, ActionTrackingServiceImpl and ThumbnailServiceImpl APIs & various test config & other changes.   
   33231: ALF-10581 - MySQLIntegrityConstraintViolationException: Cannot add or update a child row: a foreign key constraint fails ... fk_alf_cass_cnode
   - unexpected non-null child node id when creating a new node (see also THOR-906)
   - for now genericise ALF-10153 (to include at least MySQL in addition to MS SQL Server)
   33237: Fixing JUnit jar's src attachment.
   33238: Merged BRANCHES/DEV/mward/schemacomp to BRANCHES/DEV/V4.0-BUG-FIX:
      33222: ALF-12351: Separate schema descriptor files for table prefixes: alf_, avm_, JBPM_, act_
      33235: ALF-12351: Separate schema descriptor files for table prefixes: alf_, avm_, JBPM_, act_
   33239: partial fix for: ALF-10446 - Upgraded FullCalendar to support ISO8601 Zulu timezone & told it not to ignore the timezone information.
   33240: Implementation of improvement ALF-12404.
     This fix has been OK'd by Andy Hind.
   33246: Merged BRANCHES/DEV/mward/schemacomp to BRANCHES/DEV/V4.0-BUG-FIX:
      33244: ALF-12352: Add JMX support to trigger a schema check manually
   33252: Merged V3.4-BUG-FIX to V4.0-BUG-FIX (RECORD ONLY)
      33245: ALF-12366: Delete a node by deleting its row and inserting a new one with the deleted flag set
         - Forces a new ID to be generated for the deleted node
         - Makes it impossible to simultaneously delete a node and add new children in two concurrent transactions
         - Can't be merged to 4.0 due to SOLR - needs something more clever!
      33250: ALF-12366: Derek code review
   33254: Merge V3.4-BUG-FIX to V4.0 BUG-FIX
     33249 - ALF-12342 Excel 2003 Patterns.
   33256: ALF-12386 Change the simpler projects to use explicit dependencies, rather than blindly importing all of 3rd-party, to make generating downstream Maven POMs easier
   33266: ALF-12163 - mail.parameters.from does not use the from address provided
   33283: ALF-12185 SPP/Vti Mac Office checkin correction for Collaborators (Patch from Pavel Yurkevich)
   33295: Fix to SMTP authentication implementation.
   33296: Merged BRANCHES/DEV/V3.4-BUG-FIX to BRANCHES/DEV/V4.0-BUG-FIX
      33285: Fix for ALF-12336 - Share loses performance if noncachableObjectTypes are defined (page & component)
             - New and improved mechanism for dealing with cache invalidation in Share load balancing and clustering based on the Hazelcast messaging system
             - See http://www.hazelcast.com/docs.jsp - and specifically http://www.hazelcast.com/docs/1.9.4/manual/single_html/#SpringIntegration
             - Sample per node cluster config provided in custom-slingshot-application-context.xml.sample
             - Tested with an Alfresco cluster plus a Share cluster both balanced via Apache server instances
             - Significantly improved Share stability with latest SpringSurf libs and individual node performance back up to non-clustered node speed
   Modified Alfresco JLan build to use newer hazelcast JAR and also modified existing build file references to use new jar location.
   33297: Merge V3.4-BUG-FIX (3.4.8) to V4.0-BUG-FIX (4.0.1) (RECORD ONLY)
      33292: ALF-12457: Merge V3.4.4 (3.4.4.8) to V3.4-BUG-FIX (3.4.8)
         33287: ALF-12400: Merged DEV/BELARUS/V3.4-BUG-FIX-2012_01_10 to V3.4.4 (3.4.4.8)
            33284: ALF-12400 CLONE - Security risk in Web View dashlet
            The check for user role was added to Web View and Site Links dashlets to disallow to configure Web View for Consumer, Contributor, and Collaborator roles and disallow to add Site Links for Consumer role.
      33286: ALF-9514 I18N: Model constraint values need localized display names
         - Applied diff file attached to JIRA issue (with a minor compiler error correction).
           It appeared to be a merge of: DEV/SWIFT r27643, r27645, r27692, r27846 and HEAD r28405, r29364
   33298: ALF-12461 If the OOXML file contains a thumbnail image, use this for the document thumbnail, plus improve the iWorks analoguous transformer
   33305: Fix for ALF-12463 Error querying database was detected during upgrade process from 3.1 to 4.0.0.
   33306: Experiment - adding an sdk-extras target for the enterprise specific bits of the SDK.
   33308: Merged V3.4-BUG-FIX (3.4.8) to V4.0-BUG-FIX (4.0.1)
      33242: ALF-5830 show_audit.ftl template doesn't work anymore
         - ISO9075 encode the node path
      33271: ALF-9659 In auditing, nodeNameValue extractor never works on a deletion event.
         - As pre call audit is not discarded on transaction rollback, we need to be able to have access to pre call values in the post call audit application.
           One such value is the nodeName for the post call of NodeService.deleteNode()
      33274: Merged DEV to V3.4-BUG-FIX
         33273: ALF-12314: Failed to create content due to error: lockOwner is a mandatory parameter
            It is necessary to pass the correct 'lockOwner' of a rendition, for example, to use 'rr.getLockOwner()' instead of 'lockOwner'
      33277: ALF-12436: Merged PATCHES/V3.4.6 to V3.4-BUG-FIX
         33275: ALF-12426: Correction to joins in select_ContentDataByNodeIds
            - inner join to alf_node_properties first
      33279: ALF-12366: Merged PATCHES/V3.4.6 to V3.4-BUG-FIX
         33278: ALF-12393: Another indexing race condition when MySQL read committed is enabled, this time in the debug diagnostics!
      33282: Merged V3.4 to V3.4-BUG-FIX
         32979: ALF-12114: Reverse merged ALF-10282 and related changes from V3.4-BUG-FIX, which have caused serious regressions
            31840: Fix for ALF-10282 - Web Browser freezes with large xml files Web form transformation
            32341: Fix for ALF-9883 - WCM Forms: Changing 'abstract' type carries previously-added elements   
         32998: (RECORD ONLY) Merged V3.4-BUG-FIX to V3.4
            32996: ALF-12184: SchemaBootstrap must use same assumptions as PatchServiceImpl when deciding whether an alternative patch succeeded
               - Fixes regression introduced by r31972 / ALF-11489
         33084: Merged BRANCHES/DEV/BELARUS/V3.4-BUG-FIX-2011_12_06 to V3.4 (3.4.7)
            33069: ALF-12266 NPE creating content using the web form
            Locale language can be passed not only as lang_country but as only country code as well.
         33257: ALF-10340: patch.db-V2.2-CleanNodeStatuses must now have patch.db-V2.2-Upgrade-From-2.2SP1 as an alternative to allow upgrade from 2.2.8
         - That's because patch.db-V2.2-Upgrade-From-2.2SP1 exists in 2.2.8 and once run it's too late to run patch.db-V2.2-CleanNodeStatuses
         33260: Set failonany=true on distribute-installers parallel task so that the build actually fails if an installer fails to build
      33301: ALF-12464: Merged PATCHES/V3.4.5 to V3.4-BUG-FIX
         33299: ALF-12281: Memory leak in ReferenceCountingReadOnlyIndexReaderFactory
            - The diagnostic code I added to track memory leaks was actually causing some!
            - For some reason I was recording a new reference when the index reader was dereferenced rather than clearing it
            - Would affect scenarios where the main index is long-lived and there are no writes to it and lots of searches
            - Spotted by Pavel
            - Too much late night coding!
      33303: ALF-12464: Merged PATCHES/V3.4.5 to V3.4-BUG-FIX
         33302: ALF-12281: Correction to previous checkin - deal with the initial reference created by the constructor and cleared by closeIfRequired()
   33315: ALF-11214 - IMAP subsystem is not successfully restarted after incorrect modification of IMAP properties via Admin Console
   33321: Remove /hazelcast lib dir include which is no longer required as libs are now at lib root folder
   33322: ALF-2550 - Enterprise SDK files do not contain enterprise repository project.
   33323: SDK - Added Enterprise third party libs.
   33327: Merged BRANCHES/DEV/THOR1_SPRINTS to BRANCHES/DEV/V4.0-BUG-FIX:
      33324: Fix for THOR-941. Some MIME types appear as Unknown in metadata form.
   The inconsistencies in the edit mimetype form were slightly different on V4.0.1 so I tidied them up.
   33330: ALF-12487 In Mimetype Detection, if Tika detects a generic type of text/plain or XML, defer to the Alfresco filename based type (as we already do for octet stream)
   33335: Quick build fix - comment out SDK enterprise docs while I work out what's wrong.
   33353: Added more projects to Enterprise Generate Docs.
   33357: When installing a module the tool reads the war's version.properties file and will not install if the war version is outside the repoVersionMin or repoVersionMax
   33361: Merged DEV/GETHIN/FINDBUGS to V4.0-BUG-FIX
      32962: Findbugs fix: Suspicious comparison of Integer references
      32963: Findbugs fix: Call to equals() compares different types
      32968: hashcode should be hashCode
      32970: Findbugs fix: Suspicious comparison of Integer references
      32972: Findbugs fix: Suspicious comparison of Integer or Long references
      32976: Findbugs fix: .remove() incompatible with expected argument type String 
      32977: Findbugs fix:  String is incompatible with expected argument type 
      32978: Findbugs fix: Call to equals() compares different types
      I prefer this fix to the previous one I did
      32982: Findbugs fix: Call to equals() compares different types
      classDefinition.getName().equals instead of classDefinition.equals 
      32983: Findbugs fix:  String is incompatible with expected argument type java.util.Locale
      32984: Findbugs fix: An apparent infinite loop
      32985: Findbugs fix: bad month value of 12 passed to new java.util.GregorianCalendar
      This code passes a constant month value outside the expected range of 0..11 to a method.
      32986: Findbugs fix: authenticationComponent masks field from superclass
      32987: Findbugs fix: Invocation of toString on Array
      Now uses Arrays.toString()
      32988: Findbugs fix: Incorrect lazy initialization of static field
      Now uses static initialization block
      32989: Findbugs fix: Dead code: A null pointer would have been thrown before these lines
      32990: Findbugs fix: possible null pointer dereference
      Just made the code a little clearer
      32991: Findbugs fix: class defines a clone() method but the class doesn't implement Cloneable.
      32992: Findbugs fix: All equals() methods should return false if passed a null value.
      32993: Findbugs fix: Invocation of toString on Array
      Now uses Arrays.toString()
      32994: Findbugs fix: If the multiplication is done using long arithmetic, you can avoid the possibility that the result will overflow.
      33006: Findbugs fix: possible null
      Just made the code a little clearer
      33007: Findbugs fix: Invocation of toString on Array
      Now uses Arrays.toString()
      33008: Findbugs fix:  Call to String.equals(Character)
      Explicitly using String
      33009: Findbugs fix:  int converted to long and passed as absolute time to new java.util.Date(long)
      Now works after the year 2037!
      33014: Findbugs fix: Invocation of toString on Array
      Now uses Arrays.toString()
      33023: Findbugs fix: There is an apparent infinite recursive loop
      No longer
      33025: Findbugs fix: possible null
      Just made the code a little clearer
      33026: Findbugs fix: impossible null check
      Changed && to ||
      33029: Findbugs fix: Minor change to Integer.valueOf
      33126: Findbugs fix: Comparison of String objects using ==
      now used .equals
      33127: Findbugs fix: Unwritten field.  All reads of it will return the default value. 
      Now sets values in the constructor
      33128: Findbugs fix: Removed try/catch
      33129: Findbugs fix: Added an assertTrue to the unit test
      33130: Findbugs fix: Passes null for nonnull parameter
      Mocked the serviceReg and now the tests work!
      33131: Findbugs fix: Call to a collection method contains an argument with an incompatible class from that of the collection's parameter   
      33133: Findbugs fix: Deadly embrace between inner class and thread local - not eligible for garbage collection.
      Made inner class static
      33182: Findbugs fix: possible null
      Now intialises documentPaths correctly
      33184: Findbugs fix: possible null pointer (no more)
      33185: Findbugs fix: Comparison of itself
      Changed variable name to fixedValue
      33186: Findbugs fix: Possible null pointer dereference of nodePair
      Now continues
      33187: Findbugs fix: This code seems to be using non-short-circuit logic
      33188: Findbugs fix: Possible null pointer dereference of entry
      Discussed with Andy. If there's no entry then throw the exception, we can't continue
      33189: Findbugs fix: Call to a collection method contains an argument with an incompatible class from that of the collection's parameter
      It must want to remove the user
      33199: Modified the end of line character
      33209: Changed tabs for spaces
      33210: I removed the "if" because it was never called, however Brian suggested moving it to a place where it would be called!
   33363: Fix for ALF-12374 - Share sample 'share-config-custom.xml' is missing an endpoint 'activiti-admin'
   33364: Merge V3.4-BUG-FIX to V4.0-BUG-FIX
     33362 : ALF-12448 - Missing jars in enterprise SDK
   33376: Merged (RECORD ONLY) V3.4-BUG-FIX (3.4.8) to V4.0-BUG-FIX (4.0.1)
      33375: ALF-12154: Merged HEAD to V3.4-BUG-FIX (3.4.8)
         Requested to RECORD ONLY this change when merging back to HEAD/V4.0-BUG-FIX etc.
         - Removed duplicate 'List constraint display labels' values from bpm-messages*.properties and dictionarydaotest_model.properties.
           May have been introduced by ALF-9514 changes in the same area, which was also RECORD ONLY
         - Manual merge of JSON propertyLabels that exist in HEAD back into 3.4.8
         32724: OPEN : ALF-11176: Untranslated strings in Group Review and Approve Task form
         Activiti has one default transition "Next".  If there is no transition then the model builder was not finding a translation for the task outcome (and just using the english word, e.g. "Approve").  Now it looks up the translation workflowtask.outcome.[wf:outcome property]
         32943: FIXED : ALF-11176: Untranslated stings in Group Review and Approve Task form
         I've changed the way task descriptions are retrieved. You can now enter translations for them.
   33377: ALF-12509: ibooks Format
   Added quick.ibooks file
   33378: ALF-12207	IMAP: Empty file is not opened/downloaded (using IMAP Content Links) if it was uploaded to Share via fileserver
   33379: Add the TIFF mimetype
   33380: Improve the stream to Tika conversion code, following review for THOR-952
   33385: Upgrade to the latest Tika and POI, for recent bug fixes
   33387: ALF-12492 - Email with empty subject sent to Alfresco by SMTP cause Null pointer Exception
   33396: ALF-12497 - Opening and closing (not save) MS Exel 2003 file via CIFS adds new version
   33397: Merged BRANCHES/DEV/mward/schemacomp to BRANCHES/DEV/V4.0-BUG-FIX:
      33259: ALF-12354: DB2 reference files.
      33261: ALF-12354: MySQL reference files.
      33268: ALF-12354: Oracle reference files
      33310: ALF-12354: Create schema reference files for MySQL, PostgreSQL, Oracle, DB2
      33349: ALF-12354: Create schema reference files for MySQL, PostgreSQL, Oracle, DB2
      33366: ALF-12412: Schema reference files should contain schema version number
      33367: ALF-12412: Schema reference files should contain schema version number
      33373: ALF-12354: Added comment to DB2 file regarding intermittent comparison failures.
      33389: ALF-12516: Produce XML schema definition file (XSD) for schemacomp reference files.
      33390: ALF-12516: added missing file (the actual XSD!)
      33392: ALF-7260 RINF 03: Automate DB schema validation
   33401: FindFindbugs fix: Maybe Derek expected null after all
   33406: Latest SpringSurf libs - fixed use of java.util.UUID which blocks
   33407: Merged BRANCHES\DEV\V3.4-BUG-FIX to BRANCHES\DEV\V4.0-BUG-FIX
       33399: Fix for ALF-11962 Lucene queries searching on metadata (not on cm:content) with stopwords returns wrong results
   33414: ALF-11746 Webscript to expose the repository mimetypes, along with their user facing display names and their extensions
   33415: ALF-11746 Mimetype information can be available to all
   33442: Merged BRANCHES/DEV/V3.4-BUG-FIX to BRANCHES/DEV/V4.0-BUG-FIX
      33441: Latest SpringSurf libs - performance and thread safety improvements.
   33447: Fix to email test - query error exposed by recent change.
   33451: Merged BRANCHES/DEV/mward/schemacomp to BRANCHES/DEV/V4.0-BUG-FIX:
      33446: ALF-12354: fixed DB2 intermittent unique index creation problems.
      33448: ALF-7260: removed redundant code.
   33454: Merged (RECORD ONLY) V3.4-BUG-FIX (3.4.8) to V4.0-BUG-FIX (4.0.1)
      33388: Removed L10N that no longer have a default. 
      33437: ALF-9514 I18N: Model constraint values need localized display names
          - Undo changes to webclient_ja.properties that were made by r33286 for this issue.
            There were no changes in webclient.properties that did not already exist in webclient_ja.properties
            This should simplify translations.
      33453: ALF-9514 I18N: Model constraint values need localized display names
         - Tidy up mess to do with the initial r33286 revision for this issue. Lots of L10N property changes.
           I think some of my trial merges from 4.0 did not get reverted before I applied the diff file.
           Reverse merged r33437 and r33286. Note r33388 and r33375 (for ALF-12154) already included changes to try and fix the initial revision.
   33456: Merged BRANCHES/DEV/V3.4-BUG-FIX to BRANCHES/DEV/V4.0-BUG-FIX:
      33455: ALF-12410: JMX Dumps taking very long to finish
   33457: SDK build fix
   33463: Add in Enterprise Docs to SDK (again)
   33464: Merged V3.4-BUG-FIX to V4.0-BUG-FIX
      33312: ALF-12448: Merged HEAD to V3.4-BUG-FIX (3.4.8)
         33304: ALF-7542 SDK is missing spring-test.jar
            - ant script modified to copy org.springframework.test*.jar files into root\build\assemble\sdk\lib\server\dependencies   
      33382: ALF-10239 Form validation bug when content becomes invalid after XSD change
         - override isValidForSubmit function in alfresco.xforms.TextField as alfresco.xforms.Widget version does not check the max length
      33383: Fix for ALF-11791 - Multiple search on category in Share Advanced Search return no result
         Merged HEAD to BRANCHES/DEV/V3.4-BUG-FIX
            29710: SVC15: Contribution: Alfresco Share Adv Search Enhancement: Allow advanced search on category to include sub-categories in query by checking a checkbox as in Alfresco Explorer (ALF-7157)
                   A new "showSubCategoriesOption" has been added to the "category.ftl" form control, that when set to true (as shown in the example config snippet below) will display a checkbox allowing the user to request all sub categories be searched as well as the selected ones.
                     <field id="cm:categories">
                        <control>
                           <control-param name="compactMode">true</control-param>
                           <control-param name="showSubCategoriesOption">true</control-param>
                        </control>
                     </field>
            30572: Fix for ALF-7008 - Double category in Share Advanced Search return no result
      33384: Fix for ALF-12469 - Change method BaseAssociationEditor.generateFormSubmit to protected
      33400: ALF-12366: Merged PATCHES/V3.4.6 to V3.4-BUG-FIX
         33354: ALF-12393: Parent assocs must be cached with a txn ID, even when a node has no parents
            - Use left outer join in parent assocs query
         33355: ALF-12393: Fixed typo in SQL + allowed assocIndex to be null
      33413: ALF-12219: Fix CommandServlet to not mess up Document List and My Spaces List dashlets on Websphere
      33416: ALF-12411: Merged DEV to V3.4-BUG-FIX (with corrections)
         33404: Fix that introduces full Unicode character support into jBPM 3.3.1:
            - new patch that alters columns of the jBPM 3.3.1 tables to change 'TEXT' datatype to 'NVARCHAR(MAX)' (SQL Server and Generic dialects);
            - 'jbpm-upgrade.sql' patch modified to alter as in the new patch;
            - 'AlfrescoSQLServerDialect' fixed to allow create tables with 'CLOB' columns as 'NVARCHAR(MAX)' columns
            - devious chain of alternatives (suggested by Derek) introduced to make sure the right patch executes under the right circumstances
      33418: ALF-9507: Fixed possible LDAP security hole
         Now we force RFC 2254 escaping of the user DN resolution query using argument substitution, as described here   
      http://docs.oracle.com/javase/jndi/tutorial/ldap/search/search.html
      33419: ALF-9658: Corrected AFTER_INACTIVITY cache update behaviour in InMemoryTicketComponentImpl
      - Now the key is preserved on entry update and not accidentally regenerated
      33421: Merged DEV to V3.4-BUG-FIX
         32042: ALF-11448: ArrayIndexOutOfBoundsException caused by unsynchronized call in org.alfresco.repo.webdav.WebDAV.formatModifiedDate
            Creation of SimpleDateFormatter was moved to a method.
      33422: ALF-12302: /api/tags API returns badly-formed JSON
      - Resolved as suggested by MH
      33423: ALF-10312: Parameter Based Redirection
      - Now we validate that the Explorer login page redirect URL is within the context path of the application (/alfresco)
      33424: Merged DEV to V3.4-BUG-FIX
         33358: ALF-11719: Webscript fails due to colon in password
            Split basic authentication header by first colon. Remaining part is user's password.
      33425: Merged DEV to V3.4-BUG-FIX
         33359: ALF-12071: Windows 7 cannot open files stored on Alfresco mounted as a webdav network drive if the filename contains + (plus) character
            For Windows 7 we SHOULD decode the file name gotten from GET request taking into account that "+" is not encoded as "%2B" for GET request.
      33426: Merged DEV to V3.4-BUG-FIX (with corrections)
         33374: ALF-10713: Remaining dependencies repo.remote.url from outboundSMTP-context.xml and activities-feed-context.xml was removed and SysAdminParams bean was injected instead. 
            - MailActionExecuter.URLHelper returns url to alfresco using SysAdminParams.
      33427: ALF-10713: repo.remote.url no longer used in 3.4.8 so removed altogether
      33429: Merged HEAD to BRANCHES\DEV\V3.4-BUG-FIX
          31191: First fix for ALF-10741 TAG field does not support wildcard, prefix, fuzzy queries etc -> wildcard searches entered by users will fail (for ALF-12162)
      33433: ALF-12411: Fixed postUpdateScriptPatches declaration
      33435: ALF-11719: Fix Authorization and AuthorizationTest
      33436: ALF-12411: Fixed patch script paths
      33445: Fixes: ALF-12389; internationalises the tool tip strings for changing the data list sort order.
      33449: ALF-12411: Fixes from Dmitry
      - Corrected ID of patch.db-V3.4-Upgrade-JBPM
      - dependsOn property has no effect on a SchemaUpgradeScriptPatch - order controlled by schemaBootstrap.postUpdateScriptPatches   
      33459: ALF-9811: SSOAuthenticationFilter now supports basic auth as well.
   33471: ALF-12297	Emailing to document via inbound SMTP causes integrity violation
   33473: Reference schema files moved on from 5025 to 5026
   33478: Fix for ALF-12515
   33479: Fix for ALF-11116
   33481: Fix for ALF-12099
   33487: Merged BRANCHES/DEV/mward/schemacomp to BRANCHES/DEV/V4.0-BUG-FIX:
      33485: ALF-12598: Incorrect column order on indexes and primary keys must be reported to user
   33496: Merged BRANCHES/DEV/mward/schemacomp to BRANCHES/DEV/V4.0-BUG-FIX:
      33494: ALF-12412: Schema reference files should contain schema version number
   33508: Merged BRANCHES/DEV/mward/schemacomp to BRANCHES/DEV/V4.0-BUG-FIX:
      33507: ALF-12412: Added missing files
   33515: Merge (Record Only) V3.4-BUG-FIX to V4.0-BUG-FIX
     33495 : SDK Build fix - do not merge to V4.0
   33517: V4.0 version of the fix for ALF-12393
    - The node caching structure in 4.0 is version-based and there was already a check to
      ensure that in-memory vs database versions matched for all calls to get parent associations.
    - Added an additional version check for cases where the node returns no parent associations
    - Removed right outer joins associated with the 3.4 fixes (minor complexity that is no longer required)
   33555: Merged (RECORD ONLY) V3.4-BUG-FIX (3.4.8) to V4.0-BUG-FIX (4.0.1)
      33554: Merged V3.4 (3.4.8) to V3.4-BUG-FIX (3.4.9)
         33512: GERMAN: L10N Updates, fixes: ALF-12154, ALF-9514, ALF-12389
         33513: SPANISH: L10N Updates, fixes: ALF-12154, ALF-9514, ALF-12389
         33514: FRENCH: L10N Updates, fixes: ALF-12154, ALF-9514, ALF-12389
         33516: ITALIAN: L10N Updates, fixes: ALF-12154, ALF-9514, ALF-12389
         33526: JAPANESE: L10N Updates, fixes: ALF-12154, ALF-9514, ALF-12389
         33544: SPANISH: Fixes additional Spanish translation bugs
         33552: JAPANESE: Adds previously missing workflow related translations
   33560: Fix the line endings to be consistent
   33561: DOC-335 Provide a commented out example of SPP/Vti SSL Configuration in the Module
   33563: Merge (Record Only) V3.4-Bug-FIX to V4.0-BUG-FIX
     33328 : ALF-12098 Issue ALF-4010 not fully resolved. ftp transfer of a previously moved file copies it to the destination folder of the previous move
   33566: fixed a comment.
   33567: Small change to debug logging
   33568: Removed deleteConfirm flag.   Not neccessary as events are fired postCommit.
   33570: Merged V3.4-BUG-FIX to V4.0-BUG-FIX
      33550: Fixed ALF-10895 "Links, documents and folders: Unable to delete comments"
         - Made sure no "content" is sent when using HTTP DELETE to avoid proxy issues
   33574: Merged (RECORD ONLY) V3.4-BUG-FIX (3.4.8) to V4.0-BUG-FIX (4.0.1)
      33573: Merged V3.4 (3.4.8) to V3.4-BUG-FIX (3.4.9)
         33557: JAPANESE: Removes redundant string
   33576: ALF-12634 When building the edit online link to SPP/Vti, don't assume that the protocol (http/https) is the same as Share, but instead make that a module property (similar to how the port and hostname are set)
   33577: Remove un-used imports
   33584: ALF-12363: Protect against attempt to reference undefined rawPerms variable in folder-permissions WebScript controller
   33586: ALF-12405: Always show delete site icon on My Sites dashlet when viewed on IE7
   33605: Fixes: ALF-12408: Script Error when hovering on a row.
   33616: Merged BRANCHES\DEV\V3.4-BUG-FIX to BRANCHES\DEV\V4.0-BUG-FIX
       33500: Fix for ALF-12162 Searching for words with german umlaut does not show expected results
   33624: ALF-12488 - CIFS error occurs if Hazelcast Config is enabled.
   33637: ALF-11594: Disable comment field on upload dialog after version update
   33643: ALF-12243: Fixed create HTML content via HTML editor
   33649: Minor: tabs
   33650: ALF-12657: We need to set maxSavePostSize for tomcat connectors to support SSL (e.g. ModelsGet from SOLR will truncate JSON to 4096 characters)
   33662: Fix for ALF-12460
   33663: Fix for ALF-12460 (part 2)
   33664: Fix for ALF-12460 (part 2)
   33665: Fix for ALF-12443
   33672: Merged V3.4-BUG-FIX to V4.0-BUG-FIX
      33468: Merged BRANCHES/V3.4 to BRANCHES/DEV/V3.4-BUG-FIX
         33467: Enterprise overlay update for MessagesWebScript
      33470: Merged BRANCHES/V3.4 to BRANCHES/DEV/V3.4-BUG-FIX
         33469: Revert rev 33467 - not required
      33482: Fixed ALF-12373 "IE7 Specific: incorrect displaying of "Link to Rule Set" window in Alfresco Share"
      - also fixed for ie6 removed javascript error thrown on click
      33580: Minor formatting for easier debug stepping
      33581: Fixed ALF-12638: No username in an audit context after an error
      33589: ALF-12650: Merged V3.4.1 to V3.4-BUG-FIX (3.4.9)
         33588: ALF-12620 Regression. Since 3.4.1.25 / 3.4.6.7 a user can be invited to a site multiple times
            - correction to js contains function. Bug introduced in r32775
      33597: Merge DEV to V3.4-BUG-FIX
        33465 : ALF-11193 - Consumer role cannot Unscribe/subscribe the IMAP folders.
      33602: Merged BRANCHES/DEV/BELARUS/V3.4-BUG-FIX-2011_12_06 to BRANCHES/DEV/V3.4-BUG-FIX:
         32551: ALF-10133: "Doc folder" for calendar events does not clear/reset itself
      33630: Merge Dev to V3.4-BUG-FIX
         33626 : ALF-4896 - Lock icon displayed for documents with expired lock
      33633: Merge V3.4.7 (3.4.7.1) to V3.4-BUG-FIX (3.4.9)
         33609: ALF-12589 CLONE - Content Manager unable to edit content from another user sandbox - Hot Fix for 3.4.7 needed
            Changed the permissions on the user's 'preview' store so that the group of ContentManagers was granted the ContentManager permission.
            It had been granting the permission to all current users in the group individually. As a result new ContentManagers could not FLATTEN or WRITE to the preview store.
            This had already been done for the main user store a few years back.
            As the managers parameter was no longer needed the was removed.
      33647: Merged DEV to V3.4-BUG-FIX
         33629: ALF-12585: Manage System Users shows "Change Password" icon for LDAP users in search results
            Sets "isMutable" property using addPropertyResolver in UsersDialog.   
      33648: Merged DEV to V3.4-BUG-FIX
         33623: ALF-10586: CMIS: Trying to delete a multilingual document w/o translations via webscript
            There is no need to delete associations in CMISServicesImpl.deleteObject().    
      33659: Merged V3.4 to V3.4-BUG-FIX
         33594: Merged BRANCHES/DEV/BELARUS/V3.4-BUG-FIX-2011_12_06 to BRANCHES/V3.4:
            32551: ALF-10133: "Doc folder" for calendar events does not clear/reset itself
         33595: Reverse merge of the following. Should have been committed to V3.4-BUG-FIX
            33594: Merged BRANCHES/DEV/BELARUS/V3.4-BUG-FIX-2011_12_06 to BRANCHES/V3.4:
               32551: ALF-10133: "Doc folder" for calendar events does not clear/reset itself   
         33604: ALF-12597: WCMQS doesn't work out of the box
         33654: Merged DEV to V3.4
            33622: ALF-12655: Configure link isn't presented in Web View dashlet(My Dashboard page)   
               User userIsSiteManager is true by default in webview.get.js. This allows to configure Web View dashlet on user's dashbord.
            33651: ALF-12655: Configure link isn't presented in Web View dashlet(My Dashboard page)   
               Sets userIsSiteManager=false before remote call, so if remote call fails it won't give site manager role to the user.
         33655: ALF-12366: Merged PATCHES/V3.4.6 to V3.4
            33548: ALF-12393: More changes to cope with read committed DB behaviour in AbstractReindexComponent
               - Reverted r33278, 33354, 33355 and introduced more generic solution
               - NodeRefs resolving to deleted nodes in the cache cause cache cleaning and transaction retry if they resolve OK in the database
               - Cached parent assocs are thrown away for non-deleted nodes if they are empty
               - Removes the need for the outer join and special case exception handling all over the place   
            33562: ALF-12393: Further improvments
               - Lookup of NodeRef to a deleted node must always result in cache clearing and transaction retrying (due to possible read committed behaviour half way through transaction)
               - Detection and correction of stale cached negative results (VALUE_NOT_FOUND) in node cache   
            33583: ALF-12393: Rework to getNodePair(NodeRef) to have less impact on existing code but still cope with read committed
               - Look ups of deleted nodes still cause InvalidNodeRefExceptions but these have a retryable cause so that both handlers can handle and retrying transactions can recover
               - Corrections to cached negative results (because the nodesCache remembers negative results) are written straight through and the transaction does not need to be retried
      33660: Merged V3.4 to V3.4-BUG-FIX (RECORD ONLY)
         33634: ALF-12161: Merge V3.4-BUG-FIX (3.4.9) to V3.4 (3.4.8)
            33633: Merge V3.4.7 (3.4.7.1) to V3.4-BUG-FIX (3.4.9)
               33609: ALF-12589 CLONE - Content Manager unable to edit content from another user sandbox - Hot Fix for 3.4.7 needed
                  Changed the permissions on the user's 'preview' store so that the group of ContentManagers was granted the ContentManager permission.
                  It had been granting the permission to all current users in the group individually. As a result new ContentManagers could not FLATTEN or WRITE to the preview store.
                  This had already been done for the main user store a few years back.
                  As the managers parameter was no longer needed the was removed.   
         33657: ALF-12650: Merged PATCHES/V3.4.6 to V3.4
            33590: ALF-12620: Merged V3.4-BUG-FIX (3.4.9) to V3.4.6 (3.4.6.10)
               33589: ALF-12650: Merged V3.4.1 to V3.4-BUG-FIX (3.4.9)
                  33588: ALF-12620 Regression. Since 3.4.1.25 / 3.4.6.7 a user can be invited to a site multiple times
                     - correction to js contains function. Bug introduced in r32775
   33673: Fixed compilation error
   33678: Merged V3.4-BUG-FIX to V4.0-BUG-FIX
      33677: Merged V3.4 to V3.4-BUG-FIX
         33676: ALF-12436: Increase content data cache sizes to match node properties cache sizes to allow bulk loading without overflowing
   33679: Merged V3.4-BUG-FIX to V4.0-BUG-FIX (RECORD ONLY)
      33486: Merge V4.0-BUG-FIX to V3.4-BUG-FIX
        33306 - experiment to add sdk-extras
      33489: Merged V4.0-BUG-FIX to V3.4-BUG-FIX
        33322 : Enterprise SDK
        33323
        33335
        33353
        33457
        33463
      33495: SDK Build fix - do not merge to V4.0.
   33680: Merged DEV to V4.0-BUG-FIX
      33675: ALF-12379: WebLogic: alfresco fails to start: java.lang.NoSuchMethodError: org.apache.commons.lang.mutable.MutableInt.increment()V
         org.apache.commons.* package should be used as application's preferrable for WebLogic.
   33683: Merged V3.4-BUG-FIX to V4.0-BUG-FIX
      33682: Merged V3.4 to V3.4-BUG-FIX
         33681: ALF-12132: Fix Bitrock's copy of the custom lotus context. Yuck!
   33689: Fix for ALF-12437 Switching from Lucene to Solr caused subsystems to be in inconsistent state and repository to hang
   - filter out changes to read only properties when set in bulk - ie they are ignored
   33691: Fix for ALF-12667
   33693: Fix for ALF-12695 SOLR should not appear to work with AVM - it should not silently fail.
   - AVM search and indexing will raise exceptions when used with SOLR
   - removed the AVM site bootstrap associated with Share in older versions of the product.
   33701: Merged DEV to V4.0-BUG-FIX
      33697: ALF-12691: Alfresco Explorer doesn't work: java.lang.NoClassDefFoundError: org/apache/commons/lang/builder/HashCodeBuilder
         Shared library for WAS was fixed. commons-lang-2.6.jar is used.
   33704: ALF-12299 - NFS subsystem enable/disable needs a different attribute name
   33714: ALF-10229: DOS Voodoo to set ALF_HOME to the parent parent directory of the apply_amps script
   - for /D %%D IN (%~dp0..\) do set ALF_HOME=%%~dpD
   - Yuck!
   33721: Merged BRANCHES/DEV/mward/schemacomp to BRANCHES/DEV/V4.0-BUG-FIX:
      33720: Merged BRANCHES/DEV/BELARUS/V4.0-BUG-FIX-2012_01_20 to BRANCHES/DEV/mward/schemacomp:
           33631: LF-12355 : Create schema reference files for SQL Server
   33727: Test fix - fallout from removing the bootstrap for the "sitestore" AVM store
   33729: Fixes: ALF-12575 - missing i18n strings. (translations pending)
   33738: SPANISH: Translation updates based on EN r33523
   33739: Merged DEV to V4.0-BUG-FIX
      33723: ALF-10229: apply_amps.bat doesn't work properly from alfresco-enterprise-4.0.0a.zip
         Fix for apply_amps.sh to set ALF_HOME to the parent parent directory of the apply_amps script
   33746: ALF-10656 SOLR: Patches execute search during bootstrap causing deadlock 
   - Part 1: 
     - SOLR query use during bootstrap will throw an exception
     - tidy up some patch beans
     - ignore unused AVM "sitestore" in WCMPostPermissionSnapshotPatch
   33753: Remove upgrade installers from build targets


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@33758 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
2012-02-08 11:06:09 +00:00

3343 lines
115 KiB
Java

/*
* 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 <http://www.gnu.org/licenses/>. */
package org.alfresco.filesys.avm;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.StringTokenizer;
import javax.transaction.UserTransaction;
import org.alfresco.filesys.alfresco.AlfrescoDiskDriver;
import org.alfresco.filesys.config.ServerConfigurationBean;
import org.alfresco.filesys.alfresco.AlfrescoTxDiskDriver;
import org.alfresco.jlan.server.SrvSession;
import org.alfresco.jlan.server.auth.ClientInfo;
import org.alfresco.jlan.server.core.DeviceContext;
import org.alfresco.jlan.server.core.DeviceContextException;
import org.alfresco.jlan.server.filesys.AccessDeniedException;
import org.alfresco.jlan.server.filesys.DirectoryNotEmptyException;
import org.alfresco.jlan.server.filesys.DiskInterface;
import org.alfresco.jlan.server.filesys.FileAttribute;
import org.alfresco.jlan.server.filesys.FileExistsException;
import org.alfresco.jlan.server.filesys.FileInfo;
import org.alfresco.jlan.server.filesys.FileName;
import org.alfresco.jlan.server.filesys.FileOpenParams;
import org.alfresco.jlan.server.filesys.FileStatus;
import org.alfresco.jlan.server.filesys.NetworkFile;
import org.alfresco.jlan.server.filesys.PathNotFoundException;
import org.alfresco.jlan.server.filesys.SearchContext;
import org.alfresco.jlan.server.filesys.TreeConnection;
import org.alfresco.jlan.server.filesys.cache.FileState;
import org.alfresco.jlan.server.filesys.pseudo.PseudoFile;
import org.alfresco.jlan.server.filesys.pseudo.PseudoFileList;
import org.alfresco.jlan.server.filesys.pseudo.PseudoFolderNetworkFile;
import org.alfresco.jlan.util.StringList;
import org.alfresco.jlan.util.WildCard;
import org.alfresco.model.WCMAppModel;
import org.alfresco.repo.avm.CreateStoreTxnListener;
import org.alfresco.repo.avm.CreateVersionTxnListener;
import org.alfresco.repo.avm.PurgeStoreTxnListener;
import org.alfresco.repo.avm.PurgeVersionTxnListener;
import org.alfresco.repo.domain.PropertyValue;
import org.alfresco.repo.security.authentication.AuthenticationComponent;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.service.cmr.avm.AVMBadArgumentException;
import org.alfresco.service.cmr.avm.AVMExistsException;
import org.alfresco.service.cmr.avm.AVMNodeDescriptor;
import org.alfresco.service.cmr.avm.AVMNotFoundException;
import org.alfresco.service.cmr.avm.AVMService;
import org.alfresco.service.cmr.avm.AVMStoreDescriptor;
import org.alfresco.service.cmr.avm.AVMWrongTypeException;
import org.alfresco.service.cmr.avm.VersionDescriptor;
import org.alfresco.service.cmr.avm.locking.AVMLockingException;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.MimetypeService;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.security.AuthenticationService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.namespace.RegexQNamePattern;
import org.alfresco.wcm.sandbox.SandboxConstants;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.extensions.config.ConfigElement;
/**
* AVM Repository Filesystem Driver Class
* <p>
* Provides a filesystem interface for various protocols such as SMB/CIFS and FTP.
*
* @author GKSpencer
*/
public class AVMDiskDriver extends AlfrescoTxDiskDriver implements DiskInterface
{
// Logging
private static final Log logger = LogFactory.getLog(AVMDiskDriver.class);
// Configuration key names
private static final String KEY_STORE = "storePath";
private static final String KEY_VERSION = "version";
private static final String KEY_CREATE = "createStore";
// AVM path seperator
public static final char AVM_SEPERATOR = '/';
public static final String AVM_SEPERATOR_STR = "/";
// Define client role names
public static final String RoleContentManager = "ContentManager";
public static final String RoleWebProject = "WebProject";
public static final String RoleNotWebAuthor = "NotWebAuthor";
// Content manager web project role
private static final String ROLE_CONTENT_MANAGER = "ContentManager";
// File status values used in the file state cache
public static final int FileUnknown = FileStatus.Unknown;
public static final int FileNotExist = FileStatus.NotExist;
public static final int FileExists = FileStatus.FileExists;
public static final int DirectoryExists = FileStatus.DirectoryExists;
public static final int CustomFileStatus= FileStatus.MaxStatus + 1;
// Services and helpers
private AVMService m_avmService;
private MimetypeService m_mimetypeService;
private AuthenticationComponent m_authComponent;
private AuthenticationService m_authService;
private NodeService m_nodeService;
// AVM listeners
private CreateStoreTxnListener m_createStoreListener;
private PurgeStoreTxnListener m_purgeStoreListener;
private CreateVersionTxnListener m_createVerListener;
private PurgeVersionTxnListener m_purgeVerListener;
// Web project store
private String m_webProjectStore;
/**
* Default constructor
*/
public AVMDiskDriver()
{
}
/**
* Return the AVM service
*
* @return AVMService
*/
public final AVMService getAvmService()
{
return m_avmService;
}
/**
* Return the authentication service
*
* @return AuthenticationService
*/
public final AuthenticationService getAuthenticationService()
{
return m_authService;
}
/**
* Set the AVM service
*
* @param avmService
* AVMService
*/
public void setAvmService(AVMService avmService)
{
m_avmService = avmService;
}
/**
* Set the authentication component
*
* @param authComponent
* AuthenticationComponent
*/
public void setAuthenticationComponent(AuthenticationComponent authComponent)
{
m_authComponent = authComponent;
}
/**
* Set the authentication service
*
* @param authService
* AuthenticationService
*/
public void setAuthenticationService(AuthenticationService authService)
{
m_authService = authService;
}
/**
* Set the mimetype service
*
* @param mimetypeService
* MimetypeService
*/
public void setMimetypeService(MimetypeService mimetypeService)
{
m_mimetypeService = mimetypeService;
}
/**
* Set the node service
*
* @param nodeService NodeService
*/
public void setNodeService(NodeService nodeService)
{
m_nodeService = nodeService;
}
/**
* Set the create store listener
*
* @param createStoreListener
* CreateStoreTxnListener
*/
public void setCreateStoreListener(CreateStoreTxnListener createStoreListener)
{
m_createStoreListener = createStoreListener;
}
/**
* Set the purge store listener
*
* @param purgeStoreListener
* PurgeStoreTxnListener
*/
public void setPurgeStoreListener(PurgeStoreTxnListener purgeStoreListener)
{
m_purgeStoreListener = purgeStoreListener;
}
/**
* Set the create version listener
*
* @param createVersionListener
* CreateVersionTxnListener
*/
public void setCreateVersionListener(CreateVersionTxnListener createVersionListener)
{
m_createVerListener = createVersionListener;
}
/**
* Set the purge version listener
*
* @param purgeVersionListener
* PurgeVersionTxnListener
*/
public void setPurgeVersionListener(PurgeVersionTxnListener purgeVersionListener)
{
m_purgeVerListener = purgeVersionListener;
}
/**
* Set the web project store
*
* @param webStore String
*/
public void setWebProjectStore(String webStore)
{
m_webProjectStore = webStore;
}
/**
* Parse and validate the parameter string and create a device context object for this instance of the shared
* device.
*
* @param shareName String
* @param cfg ConfigElement
* @return DeviceContext
* @exception DeviceContextException
*/
public DeviceContext createContext(String shareName, ConfigElement cfg)
throws DeviceContextException
{
AVMContext context = null;
try
{
// Check if the share is a virtualization view
ConfigElement virtElem = cfg.getChild("virtualView");
if (virtElem != null)
{
// Check if virtualization view show options have been specified
int showOptions = AVMContext.ShowStagingStores + AVMContext.ShowAuthorStores;
String showAttr = virtElem.getAttribute( "stores");
if ( showAttr != null)
{
// Split the show options string
StringTokenizer tokens = new StringTokenizer( showAttr, ",");
StringList optList = new StringList();
while ( tokens.hasMoreTokens())
optList.addString( tokens.nextToken().trim().toLowerCase());
// Build the show options mask
showOptions = 0;
if ( optList.containsString("normal"))
showOptions += AVMContext.ShowNormalStores;
if ( optList.containsString("site"))
showOptions += AVMContext.ShowSiteStores;
if ( optList.containsString("author"))
showOptions += AVMContext.ShowAuthorStores;
if ( optList.containsString("preview"))
showOptions += AVMContext.ShowPreviewStores;
if ( optList.containsString("staging"))
showOptions += AVMContext.ShowStagingStores;
}
else if ( cfg.getChild("showAllSandboxes") != null)
{
// Old style show options
showOptions = AVMContext.ShowNormalStores + AVMContext.ShowSiteStores +
AVMContext.ShowAuthorStores + AVMContext.ShowPreviewStores +
AVMContext.ShowStagingStores;
}
// Create the context
context = new AVMContext(shareName, showOptions, this);
// Check if the admin user should be allowed to write to the web project staging stores
if ( cfg.getChild("adminWriteable") != null)
context.setAllowAdminStagingWrites( true);
}
else
{
// Get the store path
ConfigElement storeElement = cfg.getChild(KEY_STORE);
if (storeElement == null
|| storeElement.getValue() == null || storeElement.getValue().length() == 0)
throw new DeviceContextException("Device missing init value: " + KEY_STORE);
String storePath = storeElement.getValue();
// Get the version if specified, or default to the head version
int version = AVMContext.VERSION_HEAD;
ConfigElement versionElem = cfg.getChild(KEY_VERSION);
if (versionElem != null)
{
// Check if the version is valid
if (versionElem.getValue() == null || versionElem.getValue().length() == 0)
throw new DeviceContextException("Store version not specified");
// Validate the version id
try
{
version = Integer.parseInt(versionElem.getValue());
}
catch (NumberFormatException ex)
{
throw new DeviceContextException("Invalid store version specified, "
+ versionElem.getValue());
}
// Range check the version id
if (version < 0 && version != AVMContext.VERSION_HEAD)
throw new DeviceContextException("Invalid store version id specified, " + version);
}
// Create the context
context = new AVMContext(shareName, storePath, version);
// Check if the create flag is enabled
ConfigElement createStore = cfg.getChild(KEY_CREATE);
context.setCreateStore(createStore != null);
// Enable file state caching
//context.enableStateCache( true);
}
}
catch (Exception ex)
{
logger.error("Error during create context", ex);
// Rethrow the exception
throw new DeviceContextException("Driver setup error, " + ex.getMessage());
}
// Register the context bean
registerContext(context);
// Return the context for this shared filesystem
return context;
}
/**
* Register a device context object for this instance of the shared
* device.
*
* @param context the device context
* @param serverConfig ServerConfigurationBean
* @exception DeviceContextException
*/
@Override
public void registerContext(DeviceContext ctx)
throws DeviceContextException
{
super.registerContext(ctx);
AVMContext context = (AVMContext)ctx;
// Use the system user as the authenticated context for the filesystem initialization
try
{
AuthenticationUtil.pushAuthentication();
AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getSystemUserName());
// Wrap the initialization in a transaction
UserTransaction tx = getTransactionService().getUserTransaction(false);
try
{
// Start the transaction
if (tx != null)
tx.begin();
// Check if the share is a virtualization view
if (context.isVirtualizationView())
{
// Enable file state caching
// context.enableStateCache(serverConfig, true);
// Plug the virtualization view context into the various store/version call back listeners
// so that store/version pseudo folders can be kept in sync with AVM
m_createStoreListener.addCallback(context);
m_purgeStoreListener.addCallback(context);
m_createVerListener.addCallback(context);
m_purgeVerListener.addCallback(context);
// Create the file state for the root path, this will build the store pseudo folder list
findPseudoState( new AVMPath( ""), context);
}
else
{
// Get the store path
String storePath = context.getStorePath();
// Get the version
int version = context.isVersion();
// Validate the store path
AVMNodeDescriptor rootNode = m_avmService.lookup(version, storePath);
if (rootNode == null)
{
// Check if the store should be created
if (!context.getCreateStore()|| version != AVMContext.VERSION_HEAD)
throw new DeviceContextException("Invalid store path/version, "
+ storePath + " (" + version + ")");
// Parse the store path
String storeName = null;
String path = null;
int pos = storePath.indexOf(":/");
if (pos != -1)
{
storeName = storePath.substring(0, pos);
if (storePath.length() > pos)
path = storePath.substring(pos + 2);
}
else
storeName = storePath;
// Check if the store exists
AVMStoreDescriptor storeDesc = null;
try
{
storeDesc = m_avmService.getStore(storeName);
}
catch (AVMNotFoundException ex)
{
}
// Create a new store if it does not exist
if (storeDesc == null)
m_avmService.createStore(storeName);
// Check if there is an optional path
if (path != null)
{
// Split the path
StringTokenizer tokens = new StringTokenizer(path, AVMPath.AVM_SEPERATOR_STR);
StringList paths = new StringList();
while (tokens.hasMoreTokens())
paths.addString(tokens.nextToken());
// Create the path, or folders that do not exist
AVMPath curPath = new AVMPath(storeName, version, FileName.DOS_SEPERATOR_STR);
AVMNodeDescriptor curDesc = m_avmService.lookup(curPath.getVersion(), curPath.getAVMPath());
// Walk the path checking creating each folder as required
for (int i = 0; i < paths.numberOfStrings(); i++)
{
AVMNodeDescriptor nextDesc = null;
try
{
// Check if the child folder exists
nextDesc = m_avmService.lookup(curDesc, paths.getStringAt(i));
}
catch (AVMNotFoundException ex)
{
}
// Check if the folder exists
if (nextDesc == null)
{
// Create the new folder
m_avmService.createDirectory(curPath.getAVMPath(), paths.getStringAt(i));
// Get the details of the new folder
nextDesc = m_avmService.lookup(curDesc, paths.getStringAt(i));
}
else if (nextDesc.isFile())
throw new DeviceContextException("Path element error, not a folder, "
+ paths.getStringAt(i));
// Step to the next level
curPath.parsePath(storeName, version, curPath.getRelativePath()
+ paths.getStringAt(i) + FileName.DOS_SEPERATOR_STR);
curDesc = nextDesc;
}
}
// Validate the store path again
rootNode = m_avmService.lookup(version, storePath);
if (rootNode == null)
throw new DeviceContextException("Failed to create new store " + storePath);
}
// Enable file state caching
// context.enableStateCache(serverConfig, true);
}
// Commit the transaction
tx.commit();
tx = null;
}
catch (Exception ex)
{
logger.error("Error during create context", ex);
// Rethrow the exception
throw new DeviceContextException("Driver setup error, " + ex.getMessage(), ex);
}
finally
{
// If there is an active transaction then roll it back
if (tx != null)
{
try
{
tx.rollback();
}
catch (Exception ex)
{
logger.warn("Failed to rollback transaction", ex);
}
}
}
// Return the context for this shared filesystem
}
finally
{
AuthenticationUtil.popAuthentication();
}
}
/**
* Return a list of the available AVM store names
*
* @return StringList
*/
public final StringList getAVMStoreNames()
{
// Use the system user as the authenticated context to get the AVM store list
String currentUser = m_authComponent.getCurrentUserName();
try
{
m_authComponent.setCurrentUser(m_authComponent.getSystemUserName());
// Wrap the service request in a transaction
UserTransaction tx = getTransactionService().getUserTransaction(false);
StringList storeNames = new StringList();
try
{
// Start the transaction
if (tx != null)
tx.begin();
// Get the list of AVM stores
List<AVMStoreDescriptor> storeList = m_avmService.getStores();
if (storeList != null)
{
for (AVMStoreDescriptor storeDesc : storeList)
storeNames.addString(storeDesc.getName());
}
// Commit the transaction
if (tx != null)
tx.commit();
tx = null;
}
catch (Exception ex)
{
logger.error("Error getting store names", ex);
}
finally
{
// If there is an active transaction then roll it back
if (tx != null)
{
try
{
tx.rollback();
}
catch (Exception ex)
{
logger.warn("Failed to rollback transaction", ex);
}
}
}
// Return the list of AVM store names
return storeNames;
}
finally
{
m_authComponent.setCurrentUser(currentUser);
}
}
/**
* Get the properties for a store
*
* @param storeName
* String
* @return Map<QName, PropertyValue>
*/
protected final Map<QName, PropertyValue> getAVMStoreProperties(String storeName)
{
// Use the system user as the authenticated context to get the AVM store properties
String currentUser = m_authComponent.getCurrentUserName();
try
{
m_authComponent.setCurrentUser(m_authComponent.getSystemUserName());
// Wrap the service request in a transaction
UserTransaction tx = getTransactionService().getUserTransaction(false);
Map<QName, PropertyValue> properties = null;
try
{
// Start the transaction
if (tx != null)
tx.begin();
// Get the list of properties for AVM store
properties = m_avmService.getStoreProperties(storeName);
// Commit the transaction
if (tx != null)
tx.commit();
tx = null;
}
catch (Exception ex)
{
logger.error("Error getting store properties", ex);
}
finally
{
// If there is an active transaction then roll it back
if (tx != null)
{
try
{
tx.rollback();
}
catch (Exception ex)
{
logger.warn("Failed to rollback transaction", ex);
}
}
}
// Return the list of AVM store properties
return properties;
}
finally
{
m_authComponent.setCurrentUser(currentUser);
}
}
/**
* Build the full store path for a file/folder using the share relative path
*
* @param ctx AVMContext
* @param path String
* @param sess SrvSession
* @return AVMPath
* @exception AccessDeniedException
*/
protected final AVMPath buildStorePath(AVMContext ctx, String path, SrvSession sess)
throws AccessDeniedException
{
// Check if the AVM filesystem is a normal or virtualization view
AVMPath avmPath = null;
if (ctx.isVirtualizationView())
{
// Create a path for the virtualization view
avmPath = new AVMPath(path);
// Check that the user has access to the path
checkPathAccess( avmPath, ctx, sess);
}
else
{
// Create a path to a single store/version
avmPath = new AVMPath(ctx.getStorePath(), ctx.isVersion(), path);
}
// Return the path
return avmPath;
}
/**
* Close the file.
*
* @param sess
* Server session
* @param tree
* Tree connection.
* @param file
* Network file context.
* @exception java.io.IOException
* If an error occurs.
*/
public void closeFile(final SrvSession sess, final TreeConnection tree, final NetworkFile file) throws java.io.IOException
{
// DEBUG
if ( logger.isDebugEnabled())
logger.debug("Close file " + file.getFullName());
doInWriteTransaction(sess, new CallableIO<Void>(){
public Void call() throws IOException
{
// Close the file
file.closeFile();
// Check if the file/directory is marked for delete
if (file.hasDeleteOnClose())
{
// Check for a file or directory
if (file.isDirectory())
deleteDirectory(sess, tree, file.getFullName());
else
deleteFile(sess, tree, file.getFullName());
}
return null;
}});
}
/**
* Create a new directory on this file system.
*
* @param sess
* Server session
* @param tree
* Tree connection.
* @param params
* Directory create parameters
* @exception java.io.IOException
* If an error occurs.
*/
public void createDirectory(SrvSession sess, TreeConnection tree, FileOpenParams params) throws java.io.IOException
{
// Check if the filesystem is writable
AVMContext ctx = (AVMContext) tree.getContext();
if (ctx.isVersion() != AVMContext.VERSION_HEAD)
throw new AccessDeniedException("Cannot create " + params.getPath() + ", filesys not writable");
// Split the path to get the new folder name and relative path
final String[] paths = FileName.splitPath(params.getPath());
// Convert the relative path to a store path
final AVMPath storePath = buildStorePath(ctx, paths[0], sess);
// DEBUG
if (logger.isDebugEnabled())
logger.debug("Create directory params=" + params + ", storePath=" + storePath + ", name=" + paths[1]);
// Check if the filesystem is the virtualization view
if (ctx.isVirtualizationView())
{
if (storePath.isReadOnlyPseudoPath())
throw new AccessDeniedException("Cannot create folder in store/version layer, " + params.getPath());
else if ( storePath.isReadOnlyAccess())
throw new AccessDeniedException("Cannot create folder " + params.getPath() + ", read-only path");
}
// Create a new file
try
{
doInWriteTransaction(sess, new CallableIO<Void>(){
public Void call() throws IOException
{
// Create the new file entry
m_avmService.createDirectory(storePath.getAVMPath(), paths[1]);
return null;
}});
}
catch (AVMExistsException ex)
{
throw new FileExistsException(params.getPath());
}
catch (AVMNotFoundException ex)
{
throw new FileNotFoundException(params.getPath());
}
catch (AVMWrongTypeException ex)
{
throw new FileNotFoundException(params.getPath());
}
catch (AVMBadArgumentException ex)
{
throw new FileNotFoundException(params.getPath());
}
catch (AVMLockingException ex)
{
throw new AccessDeniedException(params.getPath());
}
catch ( org.alfresco.repo.security.permissions.AccessDeniedException ex)
{
throw new AccessDeniedException(params.getPath());
}
}
/**
* Create a new file on the file system.
*
* @param sess
* Server session
* @param tree
* Tree connection
* @param params
* File create parameters
* @return NetworkFile
* @exception java.io.IOException
* If an error occurs.
*/
public NetworkFile createFile(final SrvSession sess, TreeConnection tree, final FileOpenParams params)
throws java.io.IOException
{
// Check if the filesystem is writable
final AVMContext ctx = (AVMContext) tree.getContext();
// Split the path to get the file name and relative path
final String[] paths = FileName.splitPath(params.getPath());
// Convert the relative path to a store path
final AVMPath storePath = buildStorePath(ctx, paths[0], sess);
// DEBUG
if (logger.isDebugEnabled())
logger.debug("Create file params=" + params + ", storePath=" + storePath + ", name=" + paths[1]);
// Check if the filesystem is the virtualization view
if (ctx.isVirtualizationView())
{
if (storePath.isReadOnlyPseudoPath())
throw new AccessDeniedException("Cannot create file in store/version layer, " + params.getPath());
else if ( storePath.isReadOnlyAccess())
throw new AccessDeniedException("Cannot create file " + params.getPath() + ", read-only path");
}
else if (storePath.getVersion() != AVMContext.VERSION_HEAD)
{
throw new AccessDeniedException("Cannot create " + params.getPath() + ", filesys not writable");
}
try
{
// Create a new file
return doInWriteTransaction(sess, new CallableIO<NetworkFile>(){
public NetworkFile call() throws IOException
{
// Create the new file entry
m_avmService.createFile(storePath.getAVMPath(), paths[1]).close();
// Get the new file details
AVMPath fileStorePath = buildStorePath(ctx, params.getPath(), sess);
AVMNodeDescriptor nodeDesc = m_avmService.lookup(fileStorePath.getVersion(), fileStorePath.getAVMPath());
if (nodeDesc != null)
{
// Create the network file object for the new file
AVMNetworkFile netFile = new AVMNetworkFile(nodeDesc, fileStorePath.getAVMPath(), fileStorePath.getVersion(),
m_nodeService, m_avmService);
netFile.setGrantedAccess(NetworkFile.READWRITE);
netFile.setFullName(params.getPath());
netFile.setFileId(fileStorePath.generateFileId());
// Set the mime-type for the new file
netFile.setMimeType(m_mimetypeService.guessMimetype(paths[1]));
return netFile;
}
return null;
}});
}
catch (AVMExistsException ex)
{
throw new FileExistsException(params.getPath());
}
catch (AVMNotFoundException ex)
{
throw new FileNotFoundException(params.getPath());
}
catch (AVMWrongTypeException ex)
{
throw new FileNotFoundException(params.getPath());
}
catch (AVMBadArgumentException ex)
{
throw new FileNotFoundException(params.getPath());
}
catch (AVMLockingException ex)
{
throw new AccessDeniedException(params.getPath());
}
catch ( org.alfresco.repo.security.permissions.AccessDeniedException ex)
{
throw new AccessDeniedException(params.getPath());
}
}
/**
* Delete the directory from the filesystem.
*
* @param sess
* Server session
* @param tree
* Tree connection
* @param dir
* Directory name.
* @exception java.io.IOException
* The exception description.
*/
public void deleteDirectory(SrvSession sess, TreeConnection tree, final String dir) throws java.io.IOException
{
// Convert the relative path to a store path
AVMContext ctx = (AVMContext) tree.getContext();
final String[] paths = FileName.splitPath(dir);
final AVMPath parentPath = buildStorePath(ctx, paths[0], sess);
final AVMPath dirPath = buildStorePath(ctx, dir, sess);
// DEBUG
if (logger.isDebugEnabled())
logger.debug("Delete directory, path=" + dir + ", dirPath=" + dirPath);
// Check if the filesystem is the virtualization view
if (ctx.isVirtualizationView())
{
if (parentPath.isPseudoPath())
throw new AccessDeniedException("Cannot delete folder in store/version layer, " + dir);
else if ( parentPath.isReadOnlyAccess())
throw new AccessDeniedException("Cannot delete folder " + dir + ", read-only path");
}
// Make sure the path is to a folder before deleting it
try
{
doInWriteTransaction(sess, new CallableIO<Void>(){
public Void call() throws IOException
{
AVMNodeDescriptor nodeDesc = m_avmService.lookup(dirPath.getVersion(), dirPath.getAVMPath());
if (nodeDesc != null)
{
// Check that we are deleting a folder
if (nodeDesc.isDirectory())
{
// Make sure the directory is empty
SortedMap<String, AVMNodeDescriptor> fileList = m_avmService.getDirectoryListing(nodeDesc);
if (fileList != null && fileList.size() > 0)
throw new DirectoryNotEmptyException(dir);
// Delete the folder
m_avmService.removeNode(dirPath.getAVMPath());
}
else
throw new IOException("Delete directory path is not a directory, " + dir);
}
return null;
}});
}
catch (AVMNotFoundException ex)
{
throw new IOException("Directory not found, " + dir);
}
catch (AVMWrongTypeException ex)
{
throw new IOException("Invalid path, " + dir);
}
catch ( org.alfresco.repo.security.permissions.AccessDeniedException ex)
{
throw new AccessDeniedException("Access denied, " + dir);
}
}
/**
* Delete the specified file.
*
* @param sess
* Server session
* @param tree
* Tree connection
* @param file
* NetworkFile
* @exception java.io.IOException
* The exception description.
*/
public void deleteFile(SrvSession sess, TreeConnection tree, final String name) throws java.io.IOException
{
// Convert the relative path to a store path
AVMContext ctx = (AVMContext) tree.getContext();
final String[] paths = FileName.splitPath(name);
final AVMPath parentPath = buildStorePath(ctx, paths[0], sess);
final AVMPath filePath = buildStorePath(ctx, name, sess);
// DEBUG
if (logger.isDebugEnabled())
logger.debug("Delete file, path=" + name + ", filePath=" + filePath);
// Check if the filesystem is the virtualization view
if (ctx.isVirtualizationView())
{
if (parentPath.isPseudoPath())
throw new AccessDeniedException("Cannot delete file in store/version layer, " + name);
else if ( parentPath.isReadOnlyAccess())
throw new AccessDeniedException("Cannot delete file " + name + ", read-only path");
}
// Make sure the path is to a file before deleting it
try
{
doInWriteTransaction(sess, new CallableIO<Void>(){
public Void call() throws IOException
{
AVMNodeDescriptor nodeDesc = m_avmService.lookup(filePath.getVersion(), filePath.getAVMPath());
if (nodeDesc != null)
{
// Check that we are deleting a file
if (nodeDesc.isFile())
{
// Delete the file
m_avmService.removeNode(filePath.getAVMPath());
}
else
throw new IOException("Delete file path is not a file, " + name);
}
return null;
}});
}
catch (AVMNotFoundException ex)
{
throw new IOException("File not found, " + name);
}
catch (AVMWrongTypeException ex)
{
throw new IOException("Invalid path, " + name);
}
catch (AVMLockingException ex)
{
throw new AccessDeniedException("File locked, " + name);
}
catch ( org.alfresco.repo.security.permissions.AccessDeniedException ex)
{
throw new AccessDeniedException("Access denied, " + name);
}
}
/**
* Check if the specified file exists, and whether it is a file or directory.
*
* @param sess
* Server session
* @param tree
* Tree connection
* @param name
* java.lang.String
* @return int
* @see FileStatus
*/
public int fileExists(SrvSession sess, TreeConnection tree, String name)
{
// Convert the relative path to a store path
AVMContext ctx = (AVMContext) tree.getContext();
AVMPath storePath = null;
try
{
storePath = buildStorePath(ctx, name, sess);
}
catch ( AccessDeniedException ex)
{
// DEBUG
if ( logger.isDebugEnabled())
logger.debug("File exists check, path=" + name + " Access denied");
return FileStatus.NotExist;
}
// DEBUG
if (logger.isDebugEnabled())
logger.debug("File exists check, path=" + name + ", storePath=" + storePath);
// Check if the path is valid
int status = FileStatus.NotExist;
if (storePath.isValid() == false)
return status;
// Check if the filesystem is the virtualization view
if (ctx.isVirtualizationView() && storePath.isReadOnlyPseudoPath())
{
// Find the file state for the pseudo folder
FileState fstate = findPseudoState(storePath, ctx);
if (fstate != null)
{
// DEBUG
if (logger.isDebugEnabled())
logger.debug(" Found pseudo file " + fstate);
// Check if the pseudo file is a file or folder
if (fstate.isDirectory())
status = FileStatus.DirectoryExists;
else
status = FileStatus.FileExists;
}
else
{
// Invalid pseudo file path
status = FileStatus.NotExist;
}
// Return the file status
return status;
}
// Search for the file/folder
beginReadTransaction( sess);
AVMNodeDescriptor nodeDesc = m_avmService.lookup(storePath.getVersion(), storePath.getAVMPath());
if (nodeDesc != null)
{
// Check if the path is to a file or folder
if (nodeDesc.isDirectory())
status = FileStatus.DirectoryExists;
else
status = FileStatus.FileExists;
}
// Return the file status
return status;
}
/**
* Flush any buffered output for the specified file.
*
* @param sess
* Server session
* @param tree
* Tree connection
* @param file
* Network file context.
* @exception java.io.IOException
* The exception description.
*/
public void flushFile(SrvSession sess, TreeConnection tree, NetworkFile file) throws java.io.IOException
{
// Flush the file
file.flushFile();
}
/**
* Get the file information for the specified file.
*
* @param sess
* Server session
* @param tree
* Tree connection
* @param name
* File name/path that information is required for.
* @return File information if valid, else null
* @exception java.io.IOException
* The exception description.
*/
public FileInfo getFileInformation(SrvSession sess, TreeConnection tree, String name) throws java.io.IOException
{
// Convert the relative path to a store path
AVMContext ctx = (AVMContext) tree.getContext();
AVMPath storePath = null;
try
{
storePath = buildStorePath( ctx, name, sess);
}
catch ( Exception ex)
{
throw new FileNotFoundException( name);
}
// DEBUG
if ( logger.isDebugEnabled())
logger.debug("Get file information, path=" + name + ", storePath=" + storePath);
// Check if hte path is valid
if ( storePath.isValid() == false)
throw new FileNotFoundException( name);
// Check if the filesystem is the virtualization view
if ( ctx.isVirtualizationView() && storePath.isReadOnlyPseudoPath())
{
// Search for the pseudo path, to check for any new stores
FileState fstate = findPseudoState( storePath, ctx);
// Check if the search path is for the root, a store or version folder
if ( storePath.isRootPath())
{
// Return dummy file informatiom for the root folder, use cached timestamps
FileInfo finfo = new FileInfo( name, 0L, FileAttribute.Directory);
if ( fstate != null) {
finfo.setModifyDateTime( fstate.getModifyDateTime());
finfo.setChangeDateTime( fstate.getModifyDateTime());
}
// Return the root folder file information
return finfo;
}
else
{
// Find the pseudo file for the store/version folder
PseudoFile psFile = findPseudoFolder( storePath, ctx);
if ( psFile != null)
{
// DEBUG
if ( logger.isDebugEnabled())
logger.debug( " Found pseudo file " + psFile);
return psFile.getFileInfo();
}
else
throw new FileNotFoundException( name);
}
}
// Search for the file/folder
beginReadTransaction( sess);
FileInfo info = null;
try
{
AVMNodeDescriptor nodeDesc = m_avmService.lookup( storePath.getVersion(), storePath.getAVMPath());
if ( nodeDesc != null)
{
// Create, and fill in, the file information
info = new FileInfo();
info.setFileName( nodeDesc.getName());
if ( nodeDesc.isFile())
{
info.setFileSize( nodeDesc.getLength());
info.setAllocationSize((nodeDesc.getLength() + 512L) & 0xFFFFFFFFFFFFFE00L);
}
else
info.setFileSize( 0L);
info.setAccessDateTime( nodeDesc.getAccessDate());
info.setCreationDateTime( nodeDesc.getCreateDate());
info.setModifyDateTime( nodeDesc.getModDate());
info.setChangeDateTime( nodeDesc.getModDate());
// Build the file attributes
int attr = 0;
if ( nodeDesc.isDirectory())
attr += FileAttribute.Directory;
if ( nodeDesc.getName().startsWith( ".") ||
nodeDesc.getName().equalsIgnoreCase( "Desktop.ini") ||
nodeDesc.getName().equalsIgnoreCase( "Thumbs.db"))
attr += FileAttribute.Hidden;
// Mark the file/folder as read-only if not the head version
if ( ctx.isVersion() != AVMContext.VERSION_HEAD || storePath.isReadOnlyAccess())
attr += FileAttribute.ReadOnly;
if ( attr == 0)
attr = FileAttribute.NTNormal;
info.setFileAttributes( attr);
// Set the file id
info.setFileId( storePath.generateFileId());
// DEBUG
if ( logger.isDebugEnabled())
logger.debug(" File info=" + info);
}
}
catch ( AVMNotFoundException ex)
{
throw new FileNotFoundException( name);
}
catch ( AVMWrongTypeException ex)
{
throw new PathNotFoundException( name);
}
// Return the file information
return info;
}
/**
* Determine if the disk device is read-only.
*
* @param sess
* Server session
* @param ctx
* Device context
* @return boolean
* @exception java.io.IOException
* If an error occurs.
*/
public boolean isReadOnly(SrvSession sess, DeviceContext ctx) throws java.io.IOException
{
// Check if the version indicates the head version, only the head is writable
AVMContext avmCtx = (AVMContext) ctx;
return avmCtx.isVersion() == AVMContext.VERSION_HEAD ? true : false;
}
/**
* Open a file on the file system.
*
* @param sess
* Server session
* @param tree
* Tree connection
* @param params
* File open parameters
* @return NetworkFile
* @exception java.io.IOException
* If an error occurs.
*/
public NetworkFile openFile(SrvSession sess, TreeConnection tree, FileOpenParams params) throws java.io.IOException
{
// Convert the relative path to a store path
AVMContext ctx = (AVMContext) tree.getContext();
AVMPath storePath = buildStorePath(ctx, params.getPath(), sess);
// DEBUG
if (logger.isDebugEnabled())
logger.debug("Open file params=" + params + ", storePath=" + storePath);
// Check if the filesystem is the virtualization view
if (ctx.isVirtualizationView() && storePath.isReadOnlyPseudoPath())
{
// Check if the path is for the root, a store or version folder
if (storePath.isRootPath())
{
// Return a dummy file for the root folder
return new PseudoFolderNetworkFile(FileName.DOS_SEPERATOR_STR);
}
else
{
// Find the pseudo file for the store/version folder
PseudoFile psFile = findPseudoFolder(storePath, ctx);
if (psFile != null)
{
// DEBUG
if (logger.isDebugEnabled())
logger.debug(" Found pseudo file " + psFile);
return psFile.getFile(params.getPath());
}
else
return null;
}
}
// Search for the file/folder
beginReadTransaction( sess);
AVMNetworkFile netFile = null;
try
{
// Get the details of the file/folder
AVMNodeDescriptor nodeDesc = m_avmService.lookup(storePath.getVersion(), storePath.getAVMPath());
if (nodeDesc != null)
{
// Check if the filesystem is read-only and write access has been requested
if (storePath.getVersion() != AVMContext.VERSION_HEAD
&& (params.isReadWriteAccess() || params.isWriteOnlyAccess()))
throw new AccessDeniedException("File " + params.getPath() + " is read-only");
// Create the network file object for the opened file/folder
netFile = new AVMNetworkFile(nodeDesc, storePath.getAVMPath(), storePath.getVersion(), m_nodeService, m_avmService);
if (params.isReadOnlyAccess() || storePath.getVersion() != AVMContext.VERSION_HEAD)
netFile.setGrantedAccess(NetworkFile.READONLY);
else
netFile.setGrantedAccess(NetworkFile.READWRITE);
netFile.setFullName(params.getPath());
netFile.setFileId(storePath.generateFileId());
// Set the mime-type for the new file
netFile.setMimeType(m_mimetypeService.guessMimetype(params.getPath()));
}
else
throw new FileNotFoundException(params.getPath());
}
catch (AVMNotFoundException ex)
{
throw new FileNotFoundException(params.getPath());
}
catch (AVMWrongTypeException ex)
{
throw new FileNotFoundException(params.getPath());
}
catch ( org.alfresco.repo.security.permissions.AccessDeniedException ex)
{
throw new FileNotFoundException(params.getPath());
}
// Return the file
return netFile;
}
/**
* Read a block of data from the specified file.
*
* @param sess
* Session details
* @param tree
* Tree connection
* @param file
* Network file
* @param buf
* Buffer to return data to
* @param bufPos
* Starting position in the return buffer
* @param siz
* Maximum size of data to return
* @param filePos
* File offset to read data
* @return Number of bytes read
* @exception java.io.IOException
* The exception description.
*/
public int readFile(SrvSession sess, TreeConnection tree, NetworkFile file, byte[] buf, int bufPos, int siz,
long filePos) throws java.io.IOException
{
// Check if the file is a directory
if (file.isDirectory())
throw new AccessDeniedException();
// If the content channel is not open for the file then start a transaction
AVMNetworkFile avmFile = (AVMNetworkFile) file;
if (avmFile.hasContentChannel() == false)
beginReadTransaction( sess);
// Read the file
int rdlen = file.readFile(buf, siz, bufPos, filePos);
// If we have reached end of file return a zero length read
if (rdlen == -1)
rdlen = 0;
// Return the actual read length
return rdlen;
}
/**
* Rename the specified file.
*
* @param sess
* Server session
* @param tree
* Tree connection
* @param oldName
* java.lang.String
* @param newName
* java.lang.String
* @exception java.io.IOException
* The exception description.
*/
public void renameFile(SrvSession sess, TreeConnection tree, String oldName, String newName)
throws java.io.IOException
{
// Split the relative paths into parent and file/folder name pairs
AVMContext ctx = (AVMContext) tree.getContext();
final String[] oldPaths = FileName.splitPath(oldName);
final String[] newPaths = FileName.splitPath(newName);
// Convert the parent paths to store paths
final AVMPath oldAVMPath = buildStorePath(ctx, oldPaths[0], sess);
final AVMPath newAVMPath = buildStorePath(ctx, newPaths[0], sess);
// DEBUG
if (logger.isDebugEnabled())
{
logger.debug("Rename from path=" + oldPaths[0] + ", name=" + oldPaths[1]);
logger.debug(" new path=" + newPaths[0] + ", name=" + newPaths[1]);
}
// Check if the filesystem is the virtualization view
if (ctx.isVirtualizationView())
{
if ( oldAVMPath.isReadOnlyPseudoPath())
throw new AccessDeniedException("Cannot rename folder in store/version layer, " + oldName);
else if ( newAVMPath.isReadOnlyPseudoPath())
throw new AccessDeniedException("Cannot rename folder to store/version layer, " + newName);
else if ( oldAVMPath.isReadOnlyAccess() )
throw new AccessDeniedException("Cannot rename read-only folder, " + oldName);
else if ( newAVMPath.isReadOnlyAccess() )
throw new AccessDeniedException("Cannot rename folder to read-only folder, " + newName);
}
try
{
doInWriteTransaction(sess, new CallableIO<Void>(){
public Void call() throws IOException
{
// Rename the file/folder
m_avmService.rename(oldAVMPath.getAVMPath(), oldPaths[1], newAVMPath.getAVMPath(), newPaths[1]);
return null;
}});
}
catch (AVMNotFoundException ex)
{
throw new IOException("Source not found, " + oldName);
}
catch (AVMWrongTypeException ex)
{
throw new IOException("Invalid path, " + oldName);
}
catch (AVMExistsException ex)
{
throw new FileExistsException("Destination exists, " + newName);
}
catch ( org.alfresco.repo.security.permissions.AccessDeniedException ex)
{
throw new AccessDeniedException("Access denied, " + oldName);
}
}
/**
* Seek to the specified file position.
*
* @param sess
* Server session
* @param tree
* Tree connection
* @param file
* Network file.
* @param pos
* Position to seek to.
* @param typ
* Seek type.
* @return New file position, relative to the start of file.
*/
public long seekFile(SrvSession sess, TreeConnection tree, NetworkFile file, long pos, int typ)
throws java.io.IOException
{
// Check if the file is a directory
if (file.isDirectory())
throw new AccessDeniedException();
// If the content channel is not open for the file then start a transaction
AVMNetworkFile avmFile = (AVMNetworkFile) file;
if (avmFile.hasContentChannel() == false)
beginReadTransaction( sess);
// Set the file position
return file.seekFile(pos, typ);
}
/**
* Set the file information for the specified file.
*
* @param sess
* Server session
* @param tree
* Tree connection
* @param name
* java.lang.String
* @param info
* FileInfo
* @exception java.io.IOException
* The exception description.
*/
public void setFileInformation(SrvSession sess, TreeConnection tree, String name, FileInfo info)
throws java.io.IOException
{
// Check if the file is being marked for deletion, check if the file is writable
if (info.hasSetFlag(FileInfo.SetDeleteOnClose) && info.hasDeleteOnClose())
{
// If this is not the head version then it's not writable
AVMContext avmCtx = (AVMContext) tree.getContext();
// Parse the path
AVMPath storePath = buildStorePath(avmCtx, name, sess);
if (avmCtx.isVersion() != AVMContext.VERSION_HEAD || storePath.isReadOnlyAccess())
throw new AccessDeniedException("Store not writable, cannot set delete on close");
}
}
/**
* Start a new search on the filesystem using the specified searchPath that may contain wildcards.
*
* @param sess
* Server session
* @param tree
* Tree connection
* @param searchPath
* File(s) to search for, may include wildcards.
* @param attrib
* Attributes of the file(s) to search for, see class SMBFileAttribute.
* @return SearchContext
* @exception java.io.FileNotFoundException
* If the search could not be started.
*/
public SearchContext startSearch(SrvSession sess, TreeConnection tree, String searchPath, int attrib)
throws java.io.FileNotFoundException
{
// Access the AVM context
AVMContext avmCtx = (AVMContext) tree.getContext();
// DEBUG
if (logger.isDebugEnabled())
logger.debug("Start search path=" + searchPath);
// Split the search path into relative path and search name
String[] paths = FileName.splitPath(searchPath);
// Build the store path to the folder being searched
AVMPath storePath = null;
try
{
storePath = buildStorePath(avmCtx, paths[0], sess);
}
catch ( AccessDeniedException ex)
{
// DEBUG
if ( logger.isDebugEnabled())
logger.debug("Start search access denied");
throw new FileNotFoundException("Access denied");
}
// Check if the filesystem is the virtualization view
if (avmCtx.isVirtualizationView())
{
// Check for a search of a pseudo folder
if (storePath.isReadOnlyPseudoPath())
{
// Get the file state for the folder being searched
FileState fstate = findPseudoState(storePath, avmCtx);
if (fstate != null)
{
// Get the pseudo file list for the parent directory
PseudoFileList searchList = null;
if ( storePath.isLevel() == AVMPath.LevelId.Root)
searchList = filterPseudoFolders(avmCtx, sess, storePath, fstate);
else
searchList = fstate.getPseudoFileList();
// Check if the pseudo file list is valid
if (searchList == null)
searchList = new PseudoFileList();
// Check if this is a single file or wildcard search
if (WildCard.containsWildcards(searchPath))
{
// Create the search context, wildcard filter will take care of secondary filtering of the
// folder listing
WildCard wildCardFilter = new WildCard(paths[1], false);
return new PseudoFileListSearchContext(searchList, attrib, wildCardFilter, storePath.isReadOnlyAccess());
}
else
{
// Search the pseudo file list for the required file
PseudoFile pseudoFile = searchList.findFile(paths[1], false);
if (pseudoFile != null)
{
// Create a search context using the single file details
PseudoFileList singleList = new PseudoFileList();
singleList.addFile(pseudoFile);
return new PseudoFileListSearchContext(singleList, attrib, null, storePath.isReadOnlyAccess());
}
}
}
// File not found
throw new FileNotFoundException(searchPath);
}
else if (storePath.isLevel() == AVMPath.LevelId.HeadMetaData
|| storePath.isLevel() == AVMPath.LevelId.VersionMetaData)
{
// Return an empty file list for now
PseudoFileList metaFiles = new PseudoFileList();
return new PseudoFileListSearchContext(metaFiles, attrib, null, storePath.isReadOnlyAccess());
}
}
// Check if the path is a wildcard search
beginReadTransaction( sess);
SearchContext context = null;
if (WildCard.containsWildcards(searchPath))
{
// Get the file listing for the folder
AVMNodeDescriptor[] fileList = m_avmService.getDirectoryListingArray(storePath.getVersion(), storePath.getAVMPath(), false);
// Create the search context
if (fileList != null)
{
// DEBUG
if (logger.isDebugEnabled())
logger.debug(" Wildcard search returned " + fileList.length + " files");
// Create the search context, wildcard filter will take care of secondary filtering of the
// folder listing
WildCard wildCardFilter = new WildCard(paths[1], false);
context = new AVMSearchContext(fileList, attrib, wildCardFilter, storePath.getRelativePath(), storePath.isReadOnlyAccess());
}
}
else
{
// Single file/folder search, convert the path to a store path
try
{
storePath = buildStorePath(avmCtx, searchPath, sess);
}
catch ( AccessDeniedException ex)
{
// DEBUG
if ( logger.isDebugEnabled())
logger.debug("Start search access denied");
throw new FileNotFoundException("Access denied");
}
// Get the single file/folder details
AVMNodeDescriptor nodeDesc = m_avmService.lookup(storePath.getVersion(), storePath.getAVMPath());
if (nodeDesc != null)
{
// Create the search context for the single file/folder
context = new AVMSingleFileSearchContext(nodeDesc, storePath.getRelativePath(), storePath.isReadOnlyAccess());
}
}
// Return the search context
return context;
}
/**
* Truncate a file to the specified size
*
* @param sess
* Server session
* @param tree
* Tree connection
* @param file
* Network file details
* @param siz
* New file length
* @exception java.io.IOException
* The exception description.
*/
public void truncateFile(SrvSession sess, TreeConnection tree, final NetworkFile file, final long siz)
throws java.io.IOException
{
// Check if the file is a directory, or only has read access
if (file.getGrantedAccess() == NetworkFile.READONLY)
throw new AccessDeniedException();
// If the content channel is not open for the file then start a transaction
AVMNetworkFile avmFile = (AVMNetworkFile) file;
// Truncate or extend the file
if (avmFile.hasContentChannel() == false || avmFile.isWritable() == false)
{
doInWriteTransaction(sess, new CallableIO<Void>(){
public Void call() throws IOException
{
file.truncateFile(siz);
file.flushFile();
return null;
}});
}
else
{
file.truncateFile(siz);
file.flushFile();
}
}
/**
* Write a block of data to the file.
*
* @param sess
* Server session
* @param tree
* Tree connection
* @param file
* Network file details
* @param buf
* byte[] Data to be written
* @param bufoff
* Offset within the buffer that the data starts
* @param siz
* int Data length
* @param fileoff
* Position within the file that the data is to be written.
* @return Number of bytes actually written
* @exception java.io.IOException
* The exception description.
*/
public int writeFile(SrvSession sess, TreeConnection tree, final NetworkFile file, final byte[] buf, final int bufoff, final int siz,
final long fileoff) throws java.io.IOException
{
// Check if the file is a directory, or only has read access
if (file.isDirectory() || file.getGrantedAccess() == NetworkFile.READONLY)
throw new AccessDeniedException();
// If the content channel is not open for the file, or the channel is not writable, then start a transaction
AVMNetworkFile avmFile = (AVMNetworkFile) file;
// Write the data to the file
if (avmFile.hasContentChannel() == false || avmFile.isWritable() == false)
{
doInWriteTransaction(sess, new CallableIO<Void>(){
public Void call() throws IOException
{
file.writeFile(buf, siz, bufoff, fileoff);
return null;
}});
}
else
{
file.writeFile(buf, siz, bufoff, fileoff);
}
// Return the actual write length
return siz;
}
/**
* Connection opened to this disk device
*
* @param sess
* Server session
* @param tree
* Tree connection
*/
public void treeClosed(SrvSession sess, TreeConnection tree)
{
// Nothing to do
}
/**
* Connection closed to this device
*
* @param sess
* Server session
* @param tree
* Tree connection
*/
public void treeOpened(SrvSession sess, TreeConnection tree)
{
// Nothing to do
}
/**
* Find the pseudo file for a virtual path
*
* @param avmPath
* AVMPath
* @param avmCtx
* AVMContext
* @return PseudoFile
*/
private final PseudoFile findPseudoFolder(AVMPath avmPath, AVMContext avmCtx)
{
return findPseudoFolder(avmPath, avmCtx, true);
}
/**
* Find the pseudo file for a virtual path
*
* @param avmPath
* AVMPath
* @param avmCtx
* AVMContext
* @param generateStates
* boolean
* @return PseudoFile
*/
private final PseudoFile findPseudoFolder(AVMPath avmPath, AVMContext avmCtx, boolean generateStates)
{
// Check if the path is to a store pseudo folder
if (avmPath.isRootPath())
return null;
// Get the file state for the parent of the required folder
FileState fstate = null;
StringBuilder str = null;
PseudoFile psFile = null;
switch (avmPath.isLevel())
{
// Store root folder
case StoreRoot:
// Get the root folder file state
fstate = avmCtx.getStateCache().findFileState(FileName.DOS_SEPERATOR_STR);
if (fstate != null && fstate.hasPseudoFiles())
psFile = fstate.getPseudoFileList().findFile(avmPath.getStoreName(), false);
break;
// Versions root or Head folder
case VersionRoot:
case Head:
// Create a path to the parent store
str = new StringBuilder();
str.append(FileName.DOS_SEPERATOR);
str.append(avmPath.getStoreName());
// Find/create the file state for the store
AVMPath storePath = new AVMPath(str.toString());
fstate = findPseudoState(storePath, avmCtx);
// Find the version root or head pseudo folder
if (fstate != null)
{
if (avmPath.isLevel() == AVMPath.LevelId.Head)
psFile = fstate.getPseudoFileList().findFile(AVMPath.VersionNameHead, true);
else
psFile = fstate.getPseudoFileList().findFile(AVMPath.VersionsFolder, true);
}
break;
// Version folder
case Version:
// Create a path to the versions folder
str = new StringBuilder();
str.append(FileName.DOS_SEPERATOR);
str.append(avmPath.getStoreName());
str.append(FileName.DOS_SEPERATOR);
str.append(AVMPath.VersionsFolder);
// Find/create the file state for the store
AVMPath verrootPath = new AVMPath(str.toString());
fstate = findPseudoState(verrootPath, avmCtx);
// Find the version pseudo file
if (fstate != null)
{
// Build the version folder name string
str.setLength(0);
str.append(AVMPath.VersionFolderPrefix);
str.append(avmPath.getVersion());
// find the version folder pseduo file
psFile = fstate.getPseudoFileList().findFile(str.toString(), true);
}
break;
// Head data or metadata folder
case HeadData:
case HeadMetaData:
// Create a path to the head folder
str = new StringBuilder();
str.append(FileName.DOS_SEPERATOR);
str.append(avmPath.getStoreName());
str.append(FileName.DOS_SEPERATOR);
str.append(AVMPath.VersionNameHead);
// Find/create the file state for the store
AVMPath headPath = new AVMPath(str.toString());
fstate = findPseudoState(headPath, avmCtx);
// Find the data or metadata pseudo folder
if (fstate != null)
{
// Find the pseudo folder
if (avmPath.isLevel() == AVMPath.LevelId.HeadData)
{
psFile = fstate.getPseudoFileList().findFile(AVMPath.DataFolder, true);
}
else
{
psFile = fstate.getPseudoFileList().findFile(AVMPath.MetaDataFolder, true);
}
}
break;
// Version data or metadata folder
case VersionData:
case VersionMetaData:
// Create a path to the version folder
str = new StringBuilder();
str.append(FileName.DOS_SEPERATOR);
str.append(avmPath.getStoreName());
str.append(FileName.DOS_SEPERATOR);
str.append(AVMPath.VersionFolderPrefix);
str.append(avmPath.getVersion());
// Find/create the file state for the store
AVMPath verPath = new AVMPath(str.toString());
fstate = findPseudoState(verPath, avmCtx);
// Find the data or metadata pseudo folder
if (fstate != null)
{
// Find the pseudo folder
if (avmPath.isLevel() == AVMPath.LevelId.VersionData)
{
psFile = fstate.getPseudoFileList().findFile(AVMPath.DataFolder, true);
}
else
{
psFile = fstate.getPseudoFileList().findFile(AVMPath.MetaDataFolder, true);
}
}
break;
}
// Check if the pseudo file was not found but file states should be generated
if (psFile == null && generateStates == true)
{
// Generate the file states for the path, this is required if a request is made to a path without
// walking the folder tree
generatePseudoFolders(avmPath, avmCtx);
// Try and find the pseudo file again
psFile = findPseudoFolder(avmPath, avmCtx, false);
}
// Return the pseudo file, or null if not found
return psFile;
}
/**
* Find the file state for a pseudo folder path
*
* @param avmPath
* AVMPath
* @param avmCtx
* AVMContext
* @return FileState
*/
protected final FileState findPseudoState(AVMPath avmPath, AVMContext avmCtx)
{
// Make sure the is to a pseudo file/folder
if ( avmPath.isPseudoPath() == false)
return null;
// Check if there are any new stores to be added to the virtualization view
if ( avmCtx.hasNewStoresQueued()) {
// Get the new stores list, there is a chance another thread might get the queue, if the queue is empty
// another thread is processing it
StringList storeNames = avmCtx.getNewStoresQueue();
while ( storeNames.numberOfStrings() > 0) {
// Get the current store name
String curStoreName = storeNames.removeStringAt( 0);
// DEBUG
if ( logger.isDebugEnabled())
logger.debug("Adding new store " + curStoreName);
// Add the current store to the virtualization view
addNewStore( avmCtx, curStoreName);
}
// Get the root folder file state, update the modification timestamp
FileState rootState = avmCtx.getStateCache().findFileState( FileName.DOS_SEPERATOR_STR);
if ( rootState != null)
rootState.updateModifyDateTime();
}
// Check if the path is to a store pseudo folder
FileState fstate = null;
StringBuilder str = null;
String relPath = null;
switch ( avmPath.isLevel())
{
// Root of the hieararchy
case Root:
// Get the root path file state
fstate = avmCtx.getStateCache().findFileState( FileName.DOS_SEPERATOR_STR);
// Check if the root file state is valid
if ( fstate == null)
{
// Create a file state for the root folder
fstate = avmCtx.getStateCache().findFileState( FileName.DOS_SEPERATOR_STR, true);
fstate.setExpiryTime( FileState.NoTimeout);
fstate.setFileStatus( DirectoryExists);
// Set the modification timestamp for the root folder
fstate.updateModifyDateTime();
// Get a list of the available AVM stores
List<AVMStoreDescriptor> storeList = m_avmService.getStores();
if ( storeList != null && storeList.size() > 0)
{
// Add pseudo files for the stores
for ( AVMStoreDescriptor storeDesc : storeList)
{
// Get the properties for the current store
String storeName = storeDesc.getName();
Map<QName, PropertyValue> props = m_avmService.getStoreProperties( storeName);
// Check if the store is a main web project
if ( props.containsKey( SandboxConstants.PROP_SANDBOX_STAGING_MAIN))
{
// Get the noderef for the web project
PropertyValue prop = props.get( SandboxConstants.PROP_WEB_PROJECT_NODE_REF);
if ( prop != null) {
// Get the web project noderef
NodeRef webNodeRef = new NodeRef( prop.getStringValue());
if (m_nodeService.exists(webNodeRef))
{
// Create the web project pseudo folder
WebProjectStorePseudoFile webProjFolder = new WebProjectStorePseudoFile( storeDesc, FileName.DOS_SEPERATOR_STR + storeName, webNodeRef);
fstate.addPseudoFile( webProjFolder);
// DEBUG
if ( logger.isDebugEnabled())
logger.debug( "Found web project " + webProjFolder.getFileName());
// Get the list of content managers for this web project
List<ChildAssociationRef> mgrAssocs = m_nodeService.getChildAssocs( webNodeRef, WCMAppModel.ASSOC_WEBUSER, RegexQNamePattern.MATCH_ALL);
for ( ChildAssociationRef mgrRef : mgrAssocs)
{
// Get the child node and see if it is a content manager association
NodeRef childRef = mgrRef.getChildRef();
if ( m_nodeService.getProperty( childRef, WCMAppModel.PROP_WEBUSERROLE).equals(ROLE_CONTENT_MANAGER))
{
// Get the user name add it to the web project pseudo folder
String userName = (String) m_nodeService.getProperty( childRef, WCMAppModel.PROP_WEBUSERNAME);
webProjFolder.addUserRole( userName, WebProjectStorePseudoFile.RoleContentManager);
// DEBUG
if ( logger.isDebugEnabled())
logger.debug(" Added content manager " + userName);
}
}
}
else
{
logger.warn("AVM Store '"+storeName+"' with webProjectNodeRef that does not exist: "+webNodeRef);
}
}
}
else
{
// Check if this store is a web project sandbox
int storeType = StoreType.Normal;
String webProjName = null;
String userName = null;
if ( props.containsKey( SandboxConstants.PROP_SANDBOX_AUTHOR_MAIN))
{
// Sandbox store, linked to a web project
storeType = StoreType.WebAuthorMain;
// Get the associated web project name
webProjName = props.get( SandboxConstants.PROP_WEBSITE_NAME).getStringValue();
// Get the user name from the store name
userName = storeName.substring( webProjName.length() + 2);
}
else if ( props.containsKey( SandboxConstants.PROP_SANDBOX_AUTHOR_PREVIEW))
{
// Author preview sandbox store, linked to a web project
storeType = StoreType.WebAuthorPreview;
// Get the associated web project name
String projPlusUser = storeName.substring( 0, storeName.length() - "--preview".length());
int pos = projPlusUser.lastIndexOf("--");
if ( pos != -1)
{
webProjName = projPlusUser.substring( 0, pos);
userName = projPlusUser.substring(pos + 2);
}
}
else if ( props.containsKey( SandboxConstants.PROP_SANDBOX_WORKFLOW_PREVIEW))
{
// Staging preview sandbox store, linked to a web project
storeType = StoreType.WebStagingPreview;
}
else if ( props.containsKey( SandboxConstants.PROP_SANDBOX_STAGING_PREVIEW))
{
// Staging preview sandbox store, linked to a web project
storeType = StoreType.WebStagingPreview;
// Get the associated web project name
webProjName = storeName.substring( 0, storeName.length() - "--preview".length());
}
else if ( props.containsKey(QName.createQName(null, ".sitestore")))
{
// Site data store type
storeType = StoreType.SiteStore;
}
// DEBUG
if ( logger.isDebugEnabled())
logger.debug( "Store " + storeDesc.getName() + ", type=" + StoreType.asString( storeType) + ", webproj=" + webProjName + ", username=" + userName);
// Add a pseudo file for the current store
if ( avmCtx.showStoreType( storeType))
{
// Create the pseudo folder for the store
StorePseudoFile storeFolder = new StorePseudoFile( storeDesc, FileName.DOS_SEPERATOR_STR + storeName, storeType);
if (storeType == StoreType.WebAuthorMain || storeType == StoreType.WebAuthorPreview ||
storeType == StoreType.WebStagingMain || storeType == StoreType.WebStagingPreview)
{
storeFolder.setWebProject( webProjName);
storeFolder.setUserName( userName);
}
// Add the store pseudo folder to the root folder file list
fstate.addPseudoFile( storeFolder);
}
}
}
}
// Scan the pseudo folder list and add all publisher/reviewer user names to the web project roles list
PseudoFileList folderList = fstate.getPseudoFileList();
if ( folderList != null && folderList.numberOfFiles() > 0)
{
// Scan the pseudo folder list
for ( int i = 0; i < folderList.numberOfFiles(); i++)
{
// Check if the current pseduo file is a store folder
if ( folderList.getFileAt( i) instanceof StorePseudoFile)
{
// Check if the store has an associated web project
StorePseudoFile curFile = (StorePseudoFile) folderList.getFileAt( i);
if ( curFile.hasWebProject())
{
// Find the associated web project pseudo folder
WebProjectStorePseudoFile webProj = (WebProjectStorePseudoFile) folderList.findFile( curFile.getWebProject(), true);
if (webProj == null)
{
logger.warn("Missing web project for: "+curFile.getFileName()+" ("+curFile.getWebProject()+")");
}
else
{
// Strip the web project name from the sandbox store name and extract the user name.
// Add the user as a publisher/reviewer to the web project roles list
String userName = curFile.getFileName().substring( webProj.getFileName().length() + 2);
// If the user does not have a content manager role then add as a publisher
if ( webProj.getUserRole( userName) == WebProjectStorePseudoFile.RoleNone)
{
webProj.addUserRole( userName, WebProjectStorePseudoFile.RolePublisher);
// DEBUG
if ( logger.isDebugEnabled())
logger.debug( "Added publisher " + userName + " to " + webProj.getFileName());
}
}
}
}
}
}
}
break;
// Store folder
case StoreRoot:
// Build the path to the parent store folder
str = new StringBuilder();
str.append( FileName.DOS_SEPERATOR);
str.append( avmPath.getStoreName());
// Search for the file state for the store pseudo folder
relPath = str.toString();
fstate = avmCtx.getStateCache().findFileState( relPath);
if ( fstate == null)
{
// Create a file state for the store path
fstate = avmCtx.getStateCache().findFileState( str.toString(), true);
fstate.setFileStatus( DirectoryExists);
// Add a pseudo file for the head version
str.append( FileName.DOS_SEPERATOR);
str.append( AVMPath.VersionNameHead);
fstate.addPseudoFile( new VersionPseudoFile( AVMPath.VersionNameHead, str.toString()));
// Add a pseudo file for the version root folder
str.setLength( relPath.length() + 1);
str.append( AVMPath.VersionsFolder);
fstate.addPseudoFile( new DummyFolderPseudoFile( AVMPath.VersionsFolder, str.toString()));
}
break;
// Head folder
case Head:
// Build the path to the store head version folder
str = new StringBuilder();
str.append( FileName.DOS_SEPERATOR);
str.append( avmPath.getStoreName());
str.append( FileName.DOS_SEPERATOR);
str.append( AVMPath.VersionNameHead);
// Search for the file state for the store head version pseudo folder
relPath = str.toString();
fstate = avmCtx.getStateCache().findFileState( relPath);
if ( fstate == null)
{
// Create a file state for the store head folder path
fstate = avmCtx.getStateCache().findFileState( str.toString(), true);
fstate.setFileStatus( DirectoryExists);
// Add a pseudo file for the data pseudo folder
str.append( FileName.DOS_SEPERATOR);
str.append( AVMPath.DataFolder);
fstate.addPseudoFile( new DummyFolderPseudoFile( AVMPath.DataFolder, str.toString()));
// Add a pseudo file for the metadata pseudo folder
str.setLength( relPath.length() + 1);
str.append( AVMPath.MetaDataFolder);
fstate.addPseudoFile( new DummyFolderPseudoFile( AVMPath.MetaDataFolder, str.toString()));
}
break;
// Version root folder
case VersionRoot:
// Get the list of AVM store versions
try
{
// Build the path to the parent store folder
str = new StringBuilder();
str.append( FileName.DOS_SEPERATOR);
str.append( avmPath.getStoreName());
str.append( FileName.DOS_SEPERATOR);
str.append( AVMPath.VersionsFolder);
// Create a file state for the store path
relPath = str.toString();
fstate = avmCtx.getStateCache().findFileState( relPath, true);
fstate.setFileStatus( DirectoryExists);
// Add pseudo folders if the list is empty
if ( fstate.hasPseudoFiles() == false)
{
// Build the version folder name for the head version
StringBuilder verStr = new StringBuilder( AVMPath.VersionFolderPrefix);
verStr.append( "-1");
// Add a pseudo file for the head version
str.append( FileName.DOS_SEPERATOR);
str.append( verStr.toString());
fstate.addPseudoFile( new VersionPseudoFile( verStr.toString(), str.toString()));
// Get the list of versions for the store
List<VersionDescriptor> verList = m_avmService.getStoreVersions( avmPath.getStoreName());
// Add pseudo files for the versions to the store state
if ( verList.size() > 0)
{
for ( VersionDescriptor verDesc : verList)
{
// Generate the version string
String verName = null;
verStr.setLength( AVMPath.VersionFolderPrefix.length());
verStr.append( verDesc.getVersionID());
verName = verStr.toString();
str.setLength( relPath.length() + 1);
str.append( verName);
// Add the version pseudo folder
fstate.addPseudoFile( new VersionPseudoFile ( verName, verDesc, str.toString()));
}
}
}
}
catch ( AVMNotFoundException ex)
{
// Invalid store name
}
break;
// Version folder
case Version:
// Build the path to the store version folder
str = new StringBuilder();
str.append( FileName.DOS_SEPERATOR);
str.append( avmPath.getStoreName());
str.append( FileName.DOS_SEPERATOR);
str.append( AVMPath.VersionFolderPrefix);
str.append( avmPath.getVersion());
// Search for the file state for the version pseudo folder
relPath = str.toString();
fstate = avmCtx.getStateCache().findFileState( relPath);
if ( fstate == null)
{
// Create a file state for the version folder path
fstate = avmCtx.getStateCache().findFileState( str.toString(), true);
fstate.setFileStatus( DirectoryExists);
// Add a pseudo file for the data pseudo folder
str.append( FileName.DOS_SEPERATOR);
str.append( AVMPath.DataFolder);
fstate.addPseudoFile( new DummyFolderPseudoFile( AVMPath.DataFolder, str.toString()));
// Add a pseudo file for the metadata pseudo folder
str.setLength( relPath.length() + 1);
str.append( AVMPath.MetaDataFolder);
fstate.addPseudoFile( new DummyFolderPseudoFile( AVMPath.MetaDataFolder, str.toString()));
}
break;
}
// Return the file state
return fstate;
}
/**
* Generate the pseudo folders for the specified path
*
* @param avmPath
* AVMPath
* @param avmCtx
* AVMContext
*/
private final void generatePseudoFolders(AVMPath avmPath, AVMContext avmCtx)
{
// Create the root file state
AVMPath createPath = new AVMPath();
StringBuilder pathStr = new StringBuilder();
pathStr.append(FileName.DOS_SEPERATOR);
createPath.parsePath(pathStr.toString());
FileState rootState = findPseudoState(createPath, avmCtx);
// Check if the path has a store name
if (avmPath.getStoreName() != null)
{
// Check if the store name is valid
if (rootState.hasPseudoFiles()
&& rootState.getPseudoFileList().findFile(avmPath.getStoreName(), false) != null)
{
// Create the store file state
pathStr.append(avmPath.getStoreName());
pathStr.append(FileName.DOS_SEPERATOR);
createPath.parsePath(pathStr.toString());
findPseudoState(createPath, avmCtx);
// Add the head and version root pseudo folders
createPath.parsePath(pathStr.toString() + AVMPath.VersionNameHead);
findPseudoState(createPath, avmCtx);
createPath.parsePath(pathStr.toString() + AVMPath.VersionsFolder);
findPseudoState(createPath, avmCtx);
// Check if the path is to a version folder
if (avmPath.isLevel().ordinal() >= AVMPath.LevelId.Version.ordinal())
{
// Build the path
pathStr.append(AVMPath.VersionsFolder);
pathStr.append(FileName.DOS_SEPERATOR);
pathStr.append(AVMPath.VersionFolderPrefix);
pathStr.append(avmPath.getVersion());
createPath.parsePath(pathStr.toString());
// Generate the version folders
findPseudoState(createPath, avmCtx);
}
}
}
}
/**
* Check that the user has access to the path
*
* @param avmPath AVMPath
* @param avmCtx AVMContext
* @param sess SrvSession
* @exception AccessDeniedException
*/
private final void checkPathAccess( AVMPath avmPath, AVMContext avmCtx, SrvSession sess)
throws AccessDeniedException {
// Only enforce access checks on virtualization views
if ( avmCtx.isVirtualizationView() == false)
return;
// Get the client details for the session
ClientInfo cInfo = sess.getClientInformation();
if ( cInfo == null || cInfo.getUserName() == null || cInfo.getUserName().length() == 0)
throw new AccessDeniedException();
// Allow access to the root folder
if ( avmPath.isLevel() == AVMPath.LevelId.Root || avmPath.isLevel() == AVMPath.LevelId.HeadData || avmPath.isLevel() == AVMPath.LevelId.StoreRootPath ) {
// Allow read only access to the root, www and avm_webapps folders
avmPath.setReadOnlyAccess(true);
return;
}
// Get root file state, get the store pseudo folder details
FileState rootState = avmCtx.getStateCache().findFileState( FileName.DOS_SEPERATOR_STR);
if ( rootState == null){
// Recreate the root file state, new stores may have been added
rootState = findPseudoState( new AVMPath( FileName.DOS_SEPERATOR_STR), avmCtx);
}
// Check if there are any store pseudo folders
if ( rootState != null && rootState.hasPseudoFiles())
{
PseudoFile pseudoFolder = rootState.getPseudoFileList().findFile( avmPath.getStoreName(), false);
if ( pseudoFolder != null)
{
// Check if the pseudo folder is a web project folder or sandbox within a web project
String curUserName = m_authComponent.getCurrentUserName();
if ( pseudoFolder instanceof WebProjectStorePseudoFile)
{
// Check the users role within the web project
WebProjectStorePseudoFile webFolder = (WebProjectStorePseudoFile) pseudoFolder;
int role = webFolder.getUserRole( curUserName);
if ( role == WebProjectStorePseudoFile.RoleNone)
{
// DEBUG
if ( logger.isDebugEnabled())
logger.debug("User " + curUserName + " has no access to web project, " + webFolder.getFileName());
// User does not have access to this web project
throw new AccessDeniedException("User " + curUserName + " has no access to web project, " + webFolder.getFileName());
}
else if ( avmCtx.allowAdminStagingWrites() && cInfo.isAdministrator())
{
// DEBUG
if ( logger.isDebugEnabled())
logger.debug("User " + curUserName + " granted write access to web project, " + webFolder.getFileName());
// Allow admin write access
avmPath.setReadOnlyAccess( false);
}
else
{
// DEBUG
if ( logger.isDebugEnabled())
logger.debug("User " + curUserName + " granted read-only access to web project, " + webFolder.getFileName());
// Only allow read-only access to the staging area
avmPath.setReadOnlyAccess( true);
}
}
else if ( pseudoFolder instanceof StorePseudoFile)
{
// Check the store type
StorePseudoFile storeFolder = (StorePseudoFile) pseudoFolder;
if ( storeFolder.isStoreType() == StoreType.Normal)
return;
else if ( storeFolder.hasWebProject())
{
// Get the web project that the sandbox is linked to
WebProjectStorePseudoFile webFolder = (WebProjectStorePseudoFile) rootState.getPseudoFileList().findFile( storeFolder.getWebProject(), false);
int role = webFolder.getUserRole( curUserName);
if ( role == WebProjectStorePseudoFile.RoleNone)
{
// User does not have access to this web project
throw new AccessDeniedException("User " + curUserName + " has no access to web project, " + webFolder.getFileName() + "/" + storeFolder.getFileName());
}
else if ( role == WebProjectStorePseudoFile.RolePublisher &&
storeFolder.getUserName().equalsIgnoreCase( curUserName) == false)
{
// User does not have access to this web project
throw new AccessDeniedException("User " + curUserName + " has no access to web project, " + webFolder.getFileName() + "/" + storeFolder.getFileName());
}
}
}
}
}
else
{
// Store does not exist
throw new AccessDeniedException("Store does not exist, " + avmPath.getStoreName());
}
// DEBUG
if (logger.isDebugEnabled())
logger.debug( "Check access " + avmPath);
}
/**
* Filter the list of pseudo folders returned in a search
*
* @param avmCtx AVMContext
* @param sess SrvSession
* @param avmPath AVMPath
* @param fstate FileState
* @return PseudoFileList
*/
private final PseudoFileList filterPseudoFolders( AVMContext avmCtx, SrvSession sess, AVMPath avmPath, FileState fstate)
{
// Check if the root folder file state has any store pseudo folders
if ( fstate.hasPseudoFiles() == false)
return null;
// Get the client details for the session
ClientInfo cInfo = sess.getClientInformation();
if ( cInfo == null || cInfo.getUserName() == null || cInfo.getUserName().length() == 0)
return null;
// Check for the admin user, no need to filter the list
PseudoFileList fullList = fstate.getPseudoFileList();
if ( cInfo.isAdministrator())
return fullList;
// Create a filtered list of store pseudo folders that the user has access to
PseudoFileList filterList = new PseudoFileList();
String userName = m_authComponent.getCurrentUserName();
for ( int i = 0; i < fullList.numberOfFiles(); i++)
{
// Get the current store pseudo folder
PseudoFile pseudoFolder = fullList.getFileAt( i);
// Check if the pseudo folder is a web project folder or sandbox within a web project
if ( pseudoFolder instanceof WebProjectStorePseudoFile)
{
// Check the users role within the web project
WebProjectStorePseudoFile webFolder = (WebProjectStorePseudoFile) pseudoFolder;
if ( avmCtx.showStagingStores() && webFolder.getUserRole( userName) != WebProjectStorePseudoFile.RoleNone)
{
// User has access to this store
filterList.addFile( pseudoFolder);
}
}
else if ( pseudoFolder instanceof StorePseudoFile)
{
// Check if the store type should be included in the visible list
StorePseudoFile storeFolder = (StorePseudoFile) pseudoFolder;
if ( avmCtx.showStoreType( storeFolder.isStoreType()))
{
// Check if the user has access to this store
if ( storeFolder.hasWebProject())
{
// Get the web project that the sandbox is linked to
WebProjectStorePseudoFile webFolder = (WebProjectStorePseudoFile) fullList.findFile( storeFolder.getWebProject(), false);
if ( webFolder != null) {
int role = webFolder.getUserRole( userName);
if ( role == WebProjectStorePseudoFile.RoleContentManager && avmCtx.showStoreType( storeFolder.isStoreType()))
{
// User is a content manager, allow access to the store
filterList.addFile( storeFolder);
}
else if ( role == WebProjectStorePseudoFile.RolePublisher && avmCtx.showStoreType( storeFolder.isStoreType()))
{
// Allow access if the user owns the current folder
if ( storeFolder.getUserName().equalsIgnoreCase( userName))
filterList.addFile( storeFolder);
}
}
else if ( logger.isDebugEnabled())
logger.debug("Cannot find associated web folder for store " + storeFolder.getFileName());
}
else if ( avmCtx.showNormalStores() || avmCtx.showSiteStores())
{
// Store is not linked to a web project, allow access to the store
filterList.addFile( storeFolder);
}
}
}
}
// Return the filtered list
return filterList;
}
/**
* Add a new store to the top level folder list
*
* @param avmCtx AVMContext
* @param storeName String
*/
protected void addNewStore( AVMContext avmCtx, String storeName) {
// Get the root folder file state
FileState fstate = avmCtx.getStateCache().findFileState( FileName.DOS_SEPERATOR_STR, true);
if ( fstate == null)
return;
// Get the properties for the store
AVMStoreDescriptor storeDesc = m_avmService.getStore( storeName);
if ( storeDesc == null)
return;
Map<QName, PropertyValue> props = m_avmService.getStoreProperties( storeName);
// Check if the store is a main web project
if ( props.containsKey( SandboxConstants.PROP_SANDBOX_STAGING_MAIN))
{
// Get the noderef for the web project
PropertyValue prop = props.get( SandboxConstants.PROP_WEB_PROJECT_NODE_REF);
if ( prop != null) {
// Get the web project noderef
NodeRef webNodeRef = new NodeRef( prop.getStringValue());
if (m_nodeService.exists(webNodeRef))
{
// Create the web project pseudo folder
WebProjectStorePseudoFile webProjFolder = new WebProjectStorePseudoFile( storeDesc, FileName.DOS_SEPERATOR_STR + storeName, webNodeRef);
fstate.addPseudoFile( webProjFolder);
// DEBUG
if ( logger.isDebugEnabled())
logger.debug( " Found web project " + webProjFolder.getFileName());
// Get the list of content managers for this web project
List<ChildAssociationRef> mgrAssocs = m_nodeService.getChildAssocs( webNodeRef, WCMAppModel.ASSOC_WEBUSER, RegexQNamePattern.MATCH_ALL);
for ( ChildAssociationRef mgrRef : mgrAssocs)
{
// Get the child node and see if it is a content manager association
NodeRef childRef = mgrRef.getChildRef();
if ( m_nodeService.getProperty( childRef, WCMAppModel.PROP_WEBUSERROLE).equals(ROLE_CONTENT_MANAGER))
{
// Get the user name add it to the web project pseudo folder
String userName = (String) m_nodeService.getProperty( childRef, WCMAppModel.PROP_WEBUSERNAME);
webProjFolder.addUserRole( userName, WebProjectStorePseudoFile.RoleContentManager);
// DEBUG
if ( logger.isDebugEnabled())
logger.debug(" Added content manager " + userName);
}
}
}
else
{
logger.warn("AVM Store '"+storeName+"' with webProjectNodeRef that does not exist: "+webNodeRef);
}
}
}
else
{
// Check if this store is a web project sandbox
int storeType = StoreType.Normal;
String webProjName = null;
String userName = null;
if ( props.containsKey( SandboxConstants.PROP_SANDBOX_AUTHOR_MAIN))
{
// Sandbox store, linked to a web project
storeType = StoreType.WebAuthorMain;
// Get the associated web project name
webProjName = props.get( SandboxConstants.PROP_WEBSITE_NAME).getStringValue();
// Get the user name from teh store name
userName = storeName.substring( webProjName.length() + 2);
}
else if ( props.containsKey( SandboxConstants.PROP_SANDBOX_AUTHOR_PREVIEW))
{
// Author preview sandbox store, linked to a web project
storeType = StoreType.WebAuthorPreview;
// Get the associated web project name
String projPlusUser = storeName.substring( 0, storeName.length() - "--preview".length());
int pos = projPlusUser.lastIndexOf("--");
if ( pos != -1)
{
webProjName = projPlusUser.substring( 0, pos);
userName = projPlusUser.substring(pos + 2);
}
}
else if ( props.containsKey( SandboxConstants.PROP_SANDBOX_WORKFLOW_PREVIEW))
{
// Staging preview sandbox store, linked to a web project
storeType = StoreType.WebStagingPreview;
}
else if ( props.containsKey( SandboxConstants.PROP_SANDBOX_STAGING_PREVIEW))
{
// Staging preview sandbox store, linked to a web project
storeType = StoreType.WebStagingPreview;
// Get the associated web project name
webProjName = storeName.substring( 0, storeName.length() - "--preview".length());
}
// DEBUG
if ( logger.isDebugEnabled())
logger.debug( " Store " + storeDesc.getName() + ", type=" + StoreType.asString( storeType) + ", webproj=" + webProjName + ", username=" + userName);
// Add a pseudo file for the current store
if ( avmCtx.showStoreType( storeType))
{
// Create the pseudo folder for the store
StorePseudoFile storeFolder = new StorePseudoFile( storeDesc, FileName.DOS_SEPERATOR_STR + storeName, storeType);
if ( storeType != StoreType.Normal)
{
storeFolder.setWebProject( webProjName);
storeFolder.setUserName( userName);
// Add all publisher/reviewer user names to the web project roles list
if ( storeFolder.hasWebProject())
{
// Find the associated web project pseudo folder
PseudoFileList folderList = fstate.getPseudoFileList();
if ( folderList != null) {
// Find the associated web project
WebProjectStorePseudoFile webProj = (WebProjectStorePseudoFile) folderList.findFile( storeFolder.getWebProject(), true);
if ( webProj != null) {
// Strip the web project name from the sandbox store name and extract the user name.
// Add the user as a publisher/reviewer to the web project roles list
userName = storeFolder.getFileName().substring( webProj.getFileName().length() + 2);
// If the user does not have a content manager role then add as a publisher
if ( webProj.getUserRole( userName) == WebProjectStorePseudoFile.RoleNone)
{
webProj.addUserRole( userName, WebProjectStorePseudoFile.RolePublisher);
// DEBUG
if ( logger.isDebugEnabled())
logger.debug( " Added publisher " + userName + " to " + webProj.getFileName());
}
}
}
}
}
// Add the store pseudo folder to the root folder file list
fstate.addPseudoFile( storeFolder);
}
}
}
}