Dave Ward 9c563e35c6 Merged V3.4-BUG-FIX to HEAD
24662: 3.4.1 bug fix branch
   24718: Merged V3.3 to V3.4-BUG-FIX
      24717: Fix ALF-5555: It is impossible to edit review date from record's details page
   24719: Fix for ALF-6106: Error on Check In operation with % symbol (SPP)
   24733: Better fix for ALF-6106: Error on Check In operation with % symbol
   24734: Fix for ALF-6089: Incorrect order of fields at 'Create Series', 'Create Category' and 'Create Folder' forms   
      The name, title and description fields are now placed in their own group on the server, all other non custom rm fields are put in an 'other' group, the client side config then declares a set for each group and orders them appropriately.
   24752: Merged V3.4 to V3.4-BUG-FIX
      24751: Merged V3.3-BUG-FIX to V3.4 (RECORD ONLY)
         Restored V3.3-BUG-FIX mergeinfo, somehow truncated in revision 24274
   24753: Merged V3.3-BUG-FIX to V3.4-BUG-FIX
      23870: Merge Dev to V3.3_BUG_FIX
         ALF-4243: F5 load-balancer sending regular HTTP requests to Alfresco server causing Faces Servlet to throw java.lang.NullPointerException (MyFaces upgrade to from 1.1.5 to 1.1.7)
      23897: Additional fixes and tweaks since introduction of MyFaces 1.1.7 library.
      23919: More JSF component id related fixes.
      23945: More MyFaces1.1.7 JSF page fix ups
      23959: Another MyFaces 1.1.7 dup id issue fixed.
      24008: ALF-4243
         - Upgraded MyFaces from 1.1.7 to 1.1.8 to fix a bug seen in 1.1.7
         - Added handling for the fact that valuebound properties that result in null now cause an exception where-as they were perfectly valid in 1.1.5.
      24419: Merge from V3.3 to V3.3-BUG-FIX
         r.24418 Fix for ALF-6075. Running out of /tmp space on the server is causing uploads to fail.
   24768: Fixes ALF-6295: Allows MySQL to not be installed via unattended installer invocation
   24771: Merged BRANCHES/V3.4 to BRANCHES/DEV/V3.4-BUG-FIX:
      24767: Merged BRANCHES/V3.3 to BRANCHES/V3.4:
           24765: ALF-6547: fix intermittent test failure (AssetServiceImplTest renameFile/renameFolder) - fallout from ALF-1948
   24779: Merge V3.3 to V3.4-BUG-FIX
     24497 : ALF-3092 - deployment service - catch Throwable from Begin.
     24684 : Merge DEV/BELARUS/V3.3-BUG-FIX-2010_10_04 to V3.3
       23498 : ALF-5498 In Windows XP, placing a Folder with a Name that already Exists Removes all Content of the Existing Folder
     24749 :  ALF-6174 - Transfer Service fails with double peer assoc custom content type
     24766 : ALF-5603 - It is impossible to assign workflow from workflow console to non-admin user
   24802: Merged BRANCHES/V3.4 to BRANCHES/DEV/V3.4-BUG-FIX:
      24801: Fix for ALF-3055: "SecurityTestSuite hangs when run in DOD5015 context - failed authentication audit hangs on DB connection"
           - do failed audits in a separate thread (from a thread pool)
   24812: Fix ALF-6316: A new "spoof" multivalue text property (cm:tagScopeSummary) is now made available for TagScope nodes when accessed via the getProperty or getProperties operations on the standard node service. The values of this property take the form "<tagname>=<tagcount>". A new interceptor has been added to the node service to do this (TagScopePropertyMethodInterceptor). WQS has been tweaked to make use of this new property, and the now defunct behaviour has been removed.
   24820: Work in progress refactoring transaction handling of transfer unit tests.
   24822: Merged BRANCHES/V3.4 to BRANCHES/DEV/V3.4-BUG-FIX:
      24821: Fix for ALF-3055: "SecurityTestSuite hangs when run in DOD5015 context - failed authentication audit hangs on DB connection"
          - fix up unit tests
   24834: ALF-6468 - Update the scheduled actions folder bootstrap to use localisable names and descriptions, following the normal pattern
   24836: Added system property 'system.cache.disableImmutableSharedCaches' (false by default)
    - Equivalent to disabling Hibernate L2 cache for *immutable* entities
    - Allows distinction between mutable and immutable cache entries
   24850: Fix ALF-6562: Moved property that is used to label the WQS dashlet on the "configure site dashboard" page out of the Slingshot project and into the WQS Share Module project. Corrected its value to "Web Quick Start" rather than "WCM Quick Start".
   24857: Merged V3.4 to V3.4-BUG-FIX
      24853: Merged V3.3 to V3.4 
         24852: Fixed ALF-6573 "Incorrect name of subgroups on "Groups" page"
   24870: Removed svn:mergeinfo from root
   24873: Merged V3.3 to V3.4-BUG-FIX (RECORD ONLY)
      21789: ALF-4333: Fix
         - Updated RepoPrimaryManifestProcessorImpl so it can handle deletions that are reported by either pre-delete noderef or archived noderef (previously only handled the latter).
         - Updated TransferManifestNodeFactory so that it handles the case where the status of the node to transfer is "deleted".
         - Updated UnitTestTransferManifestNodeFactory so that it handles the change to TransferManifestNodeFactory above.
         - Added new tests for deletion cases.
      23259: Merged HEAD to V3.3
         23256: Fix ALF-4573: Start Workflow action is absent for edited document and working copy in Share
      23346: Brought WebQS module in (including build process but not installer elements yet)
      23371: "Simply" added wcmqs to installer
      23391: ALF-5367: Copy dlls into tomcat/bin as appropriate.
      23485: Merged V3.4 to V3.3 (fix backported for V3.3.x lines)
         23472: Fixed ALF-5408: SQL Server missing ON DELETE CASCADE declarations
      23515: Merged PATCHES/V3.2.0 to V3.3
         23514: ALF-5554: Merged HEAD to V3.2.0
            23153: When updating tag scopes following system shutdown/restore, be smarter about quickly skipping tag scopes that another (new) thread is currently working on
            23283: More debugging level logging for tagging updates, to help identify the problem with periodic multi-threaded test failures on bamboo
      23535: Merged V3.4 to V3.3 (complements 23517: ALF-5552)
         23508: Fixed ALF-5559: Permission interceptors can fail if Lucene returns invalid NodeRefs
      23564: ALF-5600: Merged V3.4 to V3.3
         23424: Fixes: ALF-2989 - Incorrect sideId reference in URL for event in Site Calendar Dashlet
            Adds support for displaying events that start in the past but finish in the future (previously only events that start in the future were shown)
      23586: MERGED V3.4 to V3.3
         22864: Fix for ALF-5005: "Create and edit functions on AWE become "confused""
         23042: Fix ALF-5127: Impossible to create an article/blog (WCMQS) [Must clear panel hideEvent handler if manually hiding a YUI panel]
         23561: Fixes: ALF-4569 - Removes universal override of input width box and switches the editor form panel to adjust it's width based on content rather than window size. 
                  Fixes: ALF-4570 - Adds an override for the CSS 'top' property of the form dialogue to ensure it's always below the ribbon. (Was being set automatically by the YUI widget.panel call)
         23569: Fixes: ALF-5606 - Ribbon wasn't resizing correctly after the form events.
      23630: Backport of installer
      23631: Added 64-bit & deployment installers
      23664: Fixes ALF-5691: TransferService: Multi-byte characters are not encoded correctly
      23681: Fixes ALF-5699: TransferService: Snapshot file from source repo never contains complete MLText properties
      23695: Fixed bug exposed after fixing ALF-5699. Parsing of MLText properties out of the transfer snapshot file was incorrect, and that was causing multi-lingual property values to be duplicated
      23709: ALF-5699: Fix NPE in ManifestIntegrationTest
      23734: Merged V3.4 to V3.3
         23731: Fixes for ALF-3098 and ALF-3097
             - Share - Security check on Personal Dashboard - only the owning user can view a user dashboard page
             - Share - Security issue on Customize Site Dashboard - private and moderated site dashboard pages no longer visible to non-members, customise site and dashboard pages only accessible to SiteManager
      23747: ALF-5696: Merged V3.4 to V3.3
         23585: Fixed ALF-5372 "JavaScript error on Groups management dialog with IE8 : document.getElementById is null"
      23790: Fixed ALF-3823 "Share: RSS feed can't be read: http://cds-srv.sun.com:8700/rss/update/public/sunalert_update.xml - ok with other RSS client."
      23883: Fixes ALF-5759: WQS: Attempt to copy a website section fails
      23907: Merged DEV/BELARUS/V3.3-BUG-FIX-2010_09_20 to V33
         22750: ALF-4846: Update rules are firing on inbound actions 
      23931: Undid rev 23907 (Reverse-merged /alfresco/BRANCHES/DEV/BELARUS/V3.3-BUG-FIX-2010_09_20:r22750)
      23961: Fixed ALF-5686 "Incorrect behaviour of "All" filter in "My Tasks" dashlet"
         - Variables assigned in a <#macro> shall always be assigned using <#local> (using <#assign> makes them globally available which might cause naming collisions)
      24132: Disable intermittent failing unit test
      24148: ALF-6007: Merged HEAD to V3.3
         23049: Fixed ALF-5099: Error when trying to go back in Create Web Content Wizard (only with certain XSDs)
      24263: Merged from V3.3-BUG-FIX to V3.3
      24264: V3.3-BUG-FIX to V3.3
         24262: Stress test code for ALF-5025: Support background processing of archiving
      24287: Added missing import
      24336: Merged V3.4 to V3.3
         23205: Fix for ALF-2111 - Download URLS are different on different pages, authentication fails when URL sent
      24353: Merged V3.4 to V3.3
         24352: Fix SQL fallout from ALF-6078
      24510: Merged V3.4 to V3.3
         21960: First round of date refactoring: Document Library pages now expect XML dates (ISO8601) from Share data webscripts
         21961: Share client-side I18N utility now emulates sever-side handling of doubled-up single quotes.
      24526: Merged V3.4 to V3.3
         24402: Fix for performance degredation related to ALF-3823. 
             RSS feed processing in JavaScript relies on Rhino impl of regex - this is extreemly slow as Rhino regex is by far the slowest component of the library. 
             Switched code to use the Java Regex libraries to improve performance and reduce memory usage.
      24587: Merged V3.4 to V3.3
         24564: Fix for ALF-3727: Custom permissions aren't visible in Explorer UI
      24604: Merged V3.4 to V3.3 
         24602: Build fix for RM permission model loading - collateral damage for R 24564
      24774: Merged BRANCHES/V3.4 to BRANCHES/V3.3:
         23492: Fixed ALF-5550: DB2: Unable to insert values into alf_string_value
      24813: Merged BRANCHES/V3.4 to BRANCHES/V3.3:
         24750: Limit installer builds to 2 threads
   24874: Merged V3.4 to V3.4-BUG-FIX
      24667: Resolve ALF-6202 - MT: fix offline edit (Share)
      24672: Fixes from Gloria for: ALF-6339 and ALF-6337
      24673: Merge V3.3 to V3.4
         24668 : Upgrade of large repository to latest 3.3 fails on excession of mysql table lock size
      24674: Fixes ALF-6294: Remove illegal CLI option
      24675: Fix ALF-6099: CLONE - IE6: Sometimes errors occur on almost actions in Office Add-ins. Removed linebreaks from JSON response template & prevented "undefined" entries in URL.
      24680: ALF-6120 : Version notes are lost for versioned items migrated from 2.2 to 3.4.0
      24681: Merged BRANCHES/DEV/BELARUS/V3.4-2010_12_14 to BRANCHES/V3.4:
         24609: MT - ALF-3563
         24640: MT - ALF-3563
         (merged w/ minor improvement)
      24685: Fixes ALF-6403: Change installer window height on Linux
      24688: Fix ALF-6029 (part II) 
              - MT: cannot "Show Folders" for "Data Dictionary" in Afresco Share
              - part II adds patch and removes workaround
      24689: Fixes: ALF-6219 - Incorrectly formatted variable in translation
      24691: MT: ALF-3263
              - Explorer login now fails with consistent error message ("Unable to login - unknown username/password.") if tenant does not exist or is disabled
      24692: Fixes: ALF-6370 and ALF-6225 among others - sweep of FR and DE resource bundles for quote escaping.
      24694: Fixes ALF-6424.  Erased erroneous equals sign
      24695: Fixes: ALF-6320 - removed the country specific portion of the language pack suffixes for French, German and Spanish. This enables speakers of those languages outside of those countries to benefit from the language packs.
      24696: Fix for ALF-6299: XSS attack on editing blog post with XSS data in IE6&IE7
      24700: Swaps _it_IT for _it to make Italian language pack available to Italian speakers outside of Italy.
      24703: Avoid DB2 query failure if someone passes in a made-up UUID
              - Test RunningActionRestApiTest was making up a long node UUID
              - DB2 fails to set the parameter with SQLSTATE=22001
      24706: Merged V3.4-BUG-FIX to V3.4
         24705: Fix for ALF-6365, ALF-6335
      24708: Fix ALF-6386: View Details and Edit Metadata icons are incorrect for folder
      24709: Missing first/last name handling.
      24711: Merged V3.3 to V3.4
         24710: ALF-5535 - Fix to correctly format json number values (not as numeric human readable strings)
      24713: Fix ALF-5404: It is now possible to configure who receives notifications of "Contact Us" requests by setting a configuration property on the WQS website node, such as "feedbackAssignee.Contact Request=brian"
             Also added missing Spring MVC source to 3rd Party.
      24715: Fix for ALF-6412. OOoDirect always tries to connect to port 8100.
               Formerly the ooo.port property did not exist for the OOoDirect connector. It was added in r.23182 for the soffice process, but not for the connector bean. Now added for the connector too.
      24721: Fix for ALF-6351 - Simple search breaks if override config is used and does not contain new 'repository-search' element
      24728: Fixes: ALF-5685 - Incorrect encoding of Japanese Characters
      24732: Fixes ALF-6381 and others - calendar strings appearing incorrectly. Problem was an unicode encoded comma preventing the property string being broken up into different days of the weeks or months.
      24739: Fix ALF-6545: DB2: SQLCODE=-302, SQLSTATE=22001 (testCreateMultiLingualCategoryRoots)
              - Shortened Japanese name to 14 characters
      24740: Fixes: ALF-6413 (with some translations still pending).
      24742: Update readmes.
      24744: Merged HEAD to BRANCHES/V3.4:
         24137: Fixes: ALF-5642, ALF-3892, ALF-5043 & Brings Add Event dialog in line with other forms in share by disabling the popup validation error box.
      24746: Build/test fix: PostgreSQL -AssetServiceImplTest.renameFolder
      24755: Merged V3.3 to V3.4 (RECORD ONLY)
         21789: ALF-4333: Fix
            - Updated RepoPrimaryManifestProcessorImpl so it can handle deletions that are reported by either pre-delete noderef or archived noderef (previously only handled the latter).
            - Updated TransferManifestNodeFactory so that it handles the case where the status of the node to transfer is "deleted".
            - Updated UnitTestTransferManifestNodeFactory so that it handles the change to TransferManifestNodeFactory above.
            - Added new tests for deletion cases.
         23259: Merged HEAD to V3.3
            23256: Fix ALF-4573: Start Workflow action is absent for edited document and working copy in Share
         23346: Brought WebQS module in (including build process but not installer elements yet)
         23371: "Simply" added wcmqs to installer
         23391: ALF-5367: Copy dlls into tomcat/bin as appropriate.
         23485: Merged V3.4 to V3.3 (fix backported for V3.3.x lines)
            23472: Fixed ALF-5408: SQL Server missing ON DELETE CASCADE declarations
         23515: Merged PATCHES/V3.2.0 to V3.3
            23514: ALF-5554: Merged HEAD to V3.2.0
               23153: When updating tag scopes following system shutdown/restore, be smarter about quickly skipping tag scopes that another (new) thread is currently working on
               23283: More debugging level logging for tagging updates, to help identify the problem with periodic multi-threaded test failures on bamboo
         23535: Merged V3.4 to V3.3 (complements 23517: ALF-5552)
            23508: Fixed ALF-5559: Permission interceptors can fail if Lucene returns invalid NodeRefs
         23564: ALF-5600: Merged V3.4 to V3.3
            23424: Fixes: ALF-2989 - Incorrect sideId reference in URL for event in Site Calendar Dashlet
               Adds support for displaying events that start in the past but finish in the future (previously only events that start in the future were shown)
         23586: MERGED V3.4 to V3.3
            22864: Fix for ALF-5005: "Create and edit functions on AWE become "confused""
            23042: Fix ALF-5127: Impossible to create an article/blog (WCMQS) [Must clear panel hideEvent handler if manually hiding a YUI panel]
            23561: Fixes: ALF-4569 - Removes universal override of input width box and switches the editor form panel to adjust it's width based on content rather than window size. 
                     Fixes: ALF-4570 - Adds an override for the CSS 'top' property of the form dialogue to ensure it's always below the ribbon. (Was being set automatically by the YUI widget.panel call)
            23569: Fixes: ALF-5606 - Ribbon wasn't resizing correctly after the form events.
         23630: Backport of installer
         23631: Added 64-bit & deployment installers
         23664: Fixes ALF-5691: TransferService: Multi-byte characters are not encoded correctly
         23681: Fixes ALF-5699: TransferService: Snapshot file from source repo never contains complete MLText properties
         23695: Fixed bug exposed after fixing ALF-5699. Parsing of MLText properties out of the transfer snapshot file was incorrect, and that was causing multi-lingual property values to be duplicated
         23709: ALF-5699: Fix NPE in ManifestIntegrationTest
         23734: Merged V3.4 to V3.3
            23731: Fixes for ALF-3098 and ALF-3097
                - Share - Security check on Personal Dashboard - only the owning user can view a user dashboard page
                - Share - Security issue on Customize Site Dashboard - private and moderated site dashboard pages no longer visible to non-members, customise site and dashboard pages only accessible to SiteManager
         23747: ALF-5696: Merged V3.4 to V3.3
            23585: Fixed ALF-5372 "JavaScript error on Groups management dialog with IE8 : document.getElementById is null"
         23790: Fixed ALF-3823 "Share: RSS feed can't be read: http://cds-srv.sun.com:8700/rss/update/public/sunalert_update.xml - ok with other RSS client."
         23883: Fixes ALF-5759: WQS: Attempt to copy a website section fails
         23907: Merged DEV/BELARUS/V3.3-BUG-FIX-2010_09_20 to V33
            - 22750: ALF-4846: Update rules are firing on inbound actions 
         23931: Undid rev 23907 (Reverse-merged /alfresco/BRANCHES/DEV/BELARUS/V3.3-BUG-FIX-2010_09_20:r22750)
         23961: Fixed ALF-5686 "Incorrect behaviour of "All" filter in "My Tasks" dashlet"
            - Variables assigned in a <#macro> shall always be assigned using <#local> (using <#assign> makes them globally available which might cause naming collisions)
         24132: Disable intermittent failing unit test
         24148: ALF-6007: Merged HEAD to V3.3
            23049: Fixed ALF-5099: Error when trying to go back in Create Web Content Wizard (only with certain XSDs)
         24263: Merged from V3.3-BUG-FIX to V3.3
         24264: Merged V3.3-BUG-FIX to V3.3
            24262: Stress test code for ALF-5025: Support background processing of archiving
         24287: Added missing import
         24336: Merged V3.4 to V3.3
            23205: Fix for ALF-2111 - Download URLS are different on different pages, authentication fails when URL sent
         24353: Merged V3.4 to V3.3
            24352: Fix SQL fallout from ALF-6078
         24510: Merged V3.4 to V3.3
            21960: First round of date refactoring: Document Library pages now expect XML dates (ISO8601) from Share data webscripts
            21961: Share client-side I18N utility now emulates sever-side handling of doubled-up single quotes.
         24526: Merged V3.4 to V3.3
            24402: Fix for performance degredation related to ALF-3823. 
                RSS feed processing in JavaScript relies on Rhino impl of regex - this is extreemly slow as Rhino regex is by far the slowest component of the library. 
                Switched code to use the Java Regex libraries to improve performance and reduce memory usage.
         24587: Merged V3.4 to V3.3
            24564: Fix for ALF-3727: Custom permissions aren't visible in Explorer UI
         24604: Merged V3.4 to V3.3 
            24602: Build fix for RM permission model loading - collateral damage for R 24564
      24775: Merged BRANCHES/V3.3 to BRANCHES/V3.4: (RECORD-ONLY) - already in V3.4
         24774: (RECORD-ONLY) Merged BRANCHES/V3.4 to BRANCHES/V3.3:
            23492: Fixed ALF-5550: DB2: Unable to insert values into alf_string_value
      24788: Add evaluation use message for OSX installer
      24790: Removed svn:mergeinfo on root
      24791: Fixed ALF-6560: MIME type not detected (set to application/octet-stream) when content written via FileFolderService
             - First access of content on a new file (FileFolderService.getWriter) guesses a mimetype
             - The initial mimetype guess *was* done during create, but that was expensive.
             - Added unit test to cover regression
      24803: Merged BRANCHES/DEV/dwebster/ to BRANCHES/V3.4:
         24773: DE bug fixes received from translators 10th Jan.
         24776: ES files received from translators 10th Jan
         24793: FR files received from translators 10th Jan
         24792: IT files received from translators 10th Jan
      24804: Temporarily removing Japanese language bundle
      24856: Merged BRANCHES/DEV/dwebster/ to BRANCHES/V3.4:
         24848: Latest Language updates from Translators
      24863: ALF-6029 (MT Share - repo' view after upg)
   24880: Merged V3.3 to V3.4-BUG-FIX
      24463: Fixed ALF-4398 "Path to rule set is not displayed" ($html alias was missing from a merge)
      24465: Merge V3.3 to V3.4 (RECORD ONLY)
         24463: Fixed ALF-4398 "Path to rule set is not displayed" ($html alias was missing from a merge)
      24493: Fix for Mac OS X CIFS logon problem, change UID to start at one as zero has special meaning, plus other minor fixes. JLAN-112.
      24569: Fix for ALF-5333: Webdav - Online editing of files in a folder with German umlauts does not report correct characters
      24611: Fix broken build due to merge #fail (r24460 / ALF-4015)
      24668: ALF-4557 - Upgrade of large repository to latest 3.3 fails on excession of mysql table lock size
      24707: Fix for handling of null first/last name in wiki page list
      24710: ALF-5535 - Fix to correctly format json number values (not as numeric human readable strings)
      24794: Fix for ALF-4984 - Outdated custom-slingshot-application-context.xml.sample file for share
      24798: Fix for ALF-5806: Lucene query does not return expected result.
         - Alfresco FTS now supports the prefixes ~ and = for phrase queries
      24814: Build fix after r24798: Fix for ALF-5806: Lucene query does not return expected result.
      24823: Synchronization improvements to RemoteClient and http proxy hosts
      24825: Fixed #3 of ALF-6308 "Share data issues"
         - Share falls back to use "html uploader" (in all browsers except IE) when "JSESSIONID" cookie is unreachable from javascript (like when "HttpOnly cookies" is activated on the server.
      24835: Fixed ALF-5484: Check-in does not update association
         - Copy code when copying over an existing target node was NOT processing associations
         - Fallout from refactor and subsequent fixes related to ALF-958 (Target associations aren't copied)
         - Some commented-out unit tests reintroduced
      24842: Fix for ALF-6308 item #4 - validate the redirect URL to ensure it is a relative url
      24845: Merged DEV/DAVEW/SAP to V3.3
         23874: ALF-5822: Correct Lucene throttling mechanism to prevent build up of excessive committed deltas
            - Also correct BatchProcessor's mechanism for single-threading batches with cross dependencies
            - Single-threaded batches must be sequenced in order
         23876: ALF-5822: Default lucene.indexer.mergerTargetOverlaysBlockingFactor to 2 for better write performance under load
         24022: ALF-5822: Refinement of fix
            - Don't block a thread that has already entered the prepare phase with another indexer (e.g. a cross-store commit). Otherwise it could block indefinitely and never enter the commit phase
            - Also added extra debug diagnostics and handle all Throwables on failure
         24023: ALF-5822: Minor correction to debug log message
         24421: ALF-6134: Do not export org.hibernate.jmx.StatisticsService through JMX to avoid excessive blocking under load
         24422: ALF-6135: Remove lock contention from concurrent Lucene searches
            - Added a RW Lock and Thread local-based solution to org.apache.lucene.store.FSDirectory.FSIndexInput.readInternal() to avoid contention during multiple parallel Lucene searches. This is already recognized as a bottleneck by the Lucene developers, who offer NIOFSDirectory as an alternative, which unfortunately doesn't work on Windows.
            - Added RW lock to org.apache.lucene.index.TermInfosReader.ensureIndexIsRead()
            - Threads no longer hanging in lucene searches during load tests. Woohoo!
         24423: ALF-6136: Don't call through to org.apache.log4j.NDC unless debug is enabled as it's heavily synchronized. Also avoid dynamic method invocation by using a delegate.
         24426: ALF-6138 (SURF - PARTIAL): 'Warm' the java.beans.Introspector cache for key Freemarker accessible bean classes on loading in static initializers
         24428: ALF-6139 (SURF - PARTIAL): First log in to Share is expensive due to 'lazy' dashboard creation and excessive synchronization
            - Added AVMRemoteStore.createDocuments() for creating multiple XML documents at once, all embedded within the same master XML document in the request body 
            - Added corresponding saveDocuments() methods to Store, RemoteStore, Model, ModelObjectManager and ModelObjectPersister on the Surf side 
            - Used this in PresetsManager 
            - Removed excessive synchronization from StoreModelObjectPersister 
         24429: ALF-6140 (SURF - PARTIAL): Surf tweaks to allow concurrent execution of web scripts
            - Use StrongCacheStorage instead of MruCacheStorage in RepositoryTemplateProcessor to avoid use of a synchronized cache
            - Tweak cache sizes in FreeMarkerProcessor
            - Use thread local object wrapper delegates in QNameAwareObjectWrapper and PresentationTemplateProcessor to work around synchronization in DefaultObjectWrapper
            - Swap in the same object wrapper to WrappingTemplateModel
            - Use a concurrent HashMap in ModelObjectCache and ModelHelper and remove excessive synchronization
            - Use RW locks rather than synchronized blocks in AbstractWebScript
         24431: ALF-6141: Improvements to IBatis DAO performance under load
            - Use lazyLoadingEnabled="false", enhancementEnabled="false" to avoid unnecessary blocking and generation of CGI proxies in IBATIS DAOs
            - Use useTransactionAwareDataSource="false" to prevent Spring from agressively unwrapping DBCP connections and bypassing the prepared statement cache
         24432: ALF-6142: Remove dependency between RepositoryAuthenticationDAO and Lucene
            - Reworked RepositoryAuthenticationDAO to use a node service lookup by child association QName
            - This required adding a patch to 'upgrade' the qnames of existing authentication nodes, which previously all had the same QName
         24433: ALF-6143: Remove net.sf.ehcache.use.classic.lru setting from EhCacheManagerFactoryBean and InternalEhCacheManagerFactoryBean to prevent serialization of accesses to shared caches by multiple executing threads
         24434: ALF-6144:  DirtySessionMethodInterceptor was causing contention between multiple threads calling the same DAO.
            - Unfortunately method.getAnnotation() is a synchronized call, and thus causes concurrent calls to the same method to contended with each other. 
            - Added a non-blocking cache so that DAOs can be accessed in multiple threads without contending. 
         24435: ALF-6145: Use RW Locks in Subsystem Framework
            - The operations relied on by the dynamic proxies wrapping subsystems were synchronized and thus caused contention when multiple threads were calling in to the same subsystem
            - Replaced synchronized blocks with use of read write locks, thus allowing multiple concurrent readers
         24436: ALF-6146: Regulate PermissionModel accesses with RW locks, rather than synchronized blocks and an excessive number of concurrent hashmaps.
         24438: ALF-6136: Fix build classpath
         24439: ALF-6142: Fixed seeding of admin user password
         24444: ALF-6142: Fix unit test fallout
            - InviteServiceTest needs a transaction
            - RepositoryAuthenticationDao must listen for Person username changes and update authentication node qname accordingly
            - Correction to MT handling in RepositoryAuthenticationDao
            - Repository Authentication Component must 'normalize' the username before passing it through the DAO
         24445: ALF-6145: Correction to lock handling when propagating destroy() events
         24446: ALF-6142: Add new dependencies to unit test
         24448: ALF-6142: Further fix ups
         24461: ALF-6142: Fix unit test
         24664: ALF-6408: Prevent possible deadlock during reindexing
            - waitForHeadOfQueue() now only called in beforeCommit() phase rather than afterCommit() to prevent deadlocking with Lucene throttler
            - indexes are also flushed beforehand in beforeCommit() so that indexing work can still be parallelized
            - also prevent potential deadlock caused by clearing of IndexInfo.thisThreadPreparing in a nested transaction
         24810: ALF-6653: Use read write lock in Hibernate ReadWriteCache to avoid needless contention on L2 cache reads
         24817: ALF-4725: Avoid excessive lock contention in dbcp by upgrading to 1.4
            - also upgraded commons pool
         24818: ALF-6658: Remove synchronization from LockService - transaction local collections used anyway
         24844: ALF-6681: Don't let the PostLookup job stack up in multiple threads
            - Now only executes in one thread at a time and skips scheduled slots where it is already running
      24864: Fix for ALF-5904: Explorer - Space model rights not duplicated when creating a space based on a template
         - copy service no longer uses hasPermission
         - added tests for permission copy scenarios with assorted rights
         - this fix assumed there is nothing special about templates - ie that they should always carry permissions and is the "default" copy behaviour to copy permissions if possible
      24865: ALF-6145: Fix failing unit test
      24878: ALF-6146: Correction to write lock around requiredPermissionsCache
   24881: Increment version revision


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@26792 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
2011-04-11 15:57:26 +00:00

2162 lines
79 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.repo.deploy;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeSet;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import org.alfresco.deployment.DeploymentReceiverService;
import org.alfresco.deployment.DeploymentReceiverTransport;
import org.alfresco.deployment.DeploymentToken;
import org.alfresco.deployment.DeploymentTransportOutputFilter;
import org.alfresco.deployment.FileDescriptor;
import org.alfresco.deployment.FileType;
import org.alfresco.repo.action.ActionServiceRemote;
import org.alfresco.repo.avm.AVMNodeConverter;
import org.alfresco.repo.avm.AVMNodeService;
import org.alfresco.repo.avm.util.SimplePath;
import org.alfresco.repo.domain.PropertyValue;
import org.alfresco.repo.lock.JobLockService;
import org.alfresco.repo.lock.JobLockService.JobLockRefreshCallback;
import org.alfresco.repo.remote.AVMRemoteImpl;
import org.alfresco.repo.remote.AVMSyncServiceRemote;
import org.alfresco.repo.remote.ClientTicketHolder;
import org.alfresco.repo.remote.ClientTicketHolderThread;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.cmr.action.ActionService;
import org.alfresco.service.cmr.action.ActionServiceTransport;
import org.alfresco.service.cmr.avm.AVMException;
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.deploy.DeploymentCallback;
import org.alfresco.service.cmr.avm.deploy.DeploymentEvent;
import org.alfresco.service.cmr.avm.deploy.DeploymentService;
import org.alfresco.service.cmr.avmsync.AVMDifference;
import org.alfresco.service.cmr.avmsync.AVMSyncService;
import org.alfresco.service.cmr.remote.AVMRemote;
import org.alfresco.service.cmr.remote.AVMRemoteTransport;
import org.alfresco.service.cmr.remote.AVMSyncServiceTransport;
import org.alfresco.service.cmr.repository.ContentData;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.security.AuthenticationService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.NameMatcher;
import org.alfresco.util.Pair;
import org.alfresco.util.PropertyCheck;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.remoting.rmi.RmiProxyFactoryBean;
/**
* Implementation of DeploymentService.
* @author britt
*/
public class DeploymentServiceImpl implements DeploymentService
{
private static Log fgLogger = LogFactory.getLog(DeploymentServiceImpl.class);
/**
* The local AVMService Instance.
*/
private AVMService fAVMService;
private AVMNodeService fAVMNodeService;
/**
* The local Transaction Service Instance
*/
TransactionService trxService;
/**
* The jobLockService
*/
private JobLockService jobLockService;
/**
* The Ticket holder.
*/
private ClientTicketHolder fTicketHolder;
/**
* number of concurrent sending threads
*/
private int numberOfSendingThreads = 4;
/**
* Hold the deployment lock for 3600 seconds (1 hour)
* <p>
* This is how long we will wait for a business process to complete.
* And needs to be fairly long to allow transmission of of big files
* over high latency networks.
*/
private long targetLockTimeToLive = 3600000;
/**
* Refresh the lock every minute or so
* <p>
* This is how long we keep the lock for before nudging it. So if
* this node in the cluster is shut down during deployment then
* another node can take over.
*/
private long targetLockRefreshTime = 10000;
/**
* Retry for target lock every 1 second
*/
private long targetLockRetryWait = 1000;
/**
* Retry 10000 times before giving up, basically we
* never want to give up.
*/
private int targetLockRetryCount = 10001;
/**
* The size of the output buffers
*/
private int OUTPUT_BUFFER_SIZE = 20000;
private int outputBufferSize = OUTPUT_BUFFER_SIZE;
public void init()
{
PropertyCheck.mandatory(this, "jobLockService", jobLockService);
PropertyCheck.mandatory(this, "transactionService", trxService);
PropertyCheck.mandatory(this, "avmService", fAVMService);
PropertyCheck.mandatory(this, "avmNodeService", fAVMNodeService);
}
/**
* Default constructor.
*/
public DeploymentServiceImpl()
{
fTicketHolder = new ClientTicketHolderThread();
}
/**
* Setter.
* @param service The instance to set.
*/
public void setAvmService(AVMService service)
{
fAVMService = service;
}
/**
* Setter.
* @param trxService The instance to set.
*/
public void setTransactionService(TransactionService trxService)
{
this.trxService = trxService;
}
/*
* Deploy differences to an ASR
* (non-Javadoc)
* @see org.alfresco.service.cmr.avm.deploy.DeploymentService#deployDifference(int, java.lang.String, java.lang.String, int, java.lang.String, java.lang.String, java.lang.String, boolean, boolean)
*/
public void deployDifference(int version,
String srcPath,
String hostName,
int port,
String userName,
String password,
String dstPath,
final NameMatcher matcher,
boolean createDst,
final boolean dontDelete,
final boolean dontDo,
final List<DeploymentCallback> callbacks)
{
final String storeName = srcPath.substring(0, srcPath.indexOf(":"));
/**
* Lock the cluster for the remote target
*/
String lockStr = hostName + "." + "asr." + storeName;
QName lockQName = QName.createQName("{http://www.alfresco.org/deploymentService/1.0}" + lockStr);
Lock lock = new Lock(lockQName);
lock.makeLock();
try
{
/**
* Got the lock - now do a deployment
*/
if (fgLogger.isDebugEnabled())
{
fgLogger.debug("Deploying to Remote Alfresco at " + hostName);
}
try
{
RetryingTransactionHelper trn = trxService.getRetryingTransactionHelper();
fgLogger.debug("Connecting to remote AVM at " + hostName + ":" +port);
final AVMRemote remote = getRemote(hostName, port, userName, password);
if (version < 0)
{
/**
* If version is -1, Create a local snapshot to deploy
*/
fgLogger.debug("creating snapshot of local version");
RetryingTransactionCallback<Integer> localSnapshot = new RetryingTransactionCallback<Integer>()
{
public Integer execute() throws Throwable
{
int newVersion = fAVMService.createSnapshot(storeName, null, null).get(storeName);
return new Integer(newVersion);
}
};
version = trn.doInTransaction(localSnapshot, false, true).intValue();
fgLogger.debug("snapshot local created " + storeName + ", " + version);
}
{
DeploymentEvent event = new DeploymentEvent(DeploymentEvent.Type.START,
new Pair<Integer, String>(version, srcPath),
dstPath);
processEvent(event, callbacks);
}
/*
* Create a snapshot on the destination server.
*/
boolean createdRoot = false;
String [] storePath = dstPath.split(":");
int snapshot = -1;
// Get the root of the deployment on the destination server.
AVMNodeDescriptor dstRoot = remote.lookup(-1, dstPath);
if (!dontDo)
{
// Get the root of the deployment on the destination server.
if (dstRoot == null)
{
if (createDst)
{
fgLogger.debug("Create destination parent folder:" + dstPath);
createDestination(remote, dstPath);
dstRoot = remote.lookup(-1, dstPath);
createdRoot = true;
}
else
{
throw new AVMNotFoundException("Node Not Found: " + dstRoot);
}
}
fgLogger.debug("create snapshot on remote");
snapshot = remote.createSnapshot(storePath[0], "PreDeploy", "Pre Deployment Snapshot").get(storePath[0]);
fgLogger.debug("snapshot created on remote");
}
final int srcVersion = version;
final String srcFinalPath = srcPath;
RetryingTransactionCallback<AVMNodeDescriptor> readRoot = new RetryingTransactionCallback<AVMNodeDescriptor>()
{
public AVMNodeDescriptor execute() throws Throwable
{
return fAVMService.lookup(srcVersion, srcFinalPath);
}
};
final AVMNodeDescriptor srcRoot = trn.doInTransaction(readRoot, true, true);
// Get the root of the deployment from this server.
// AVMNodeDescriptor srcRoot = fAVMService.lookup(version, srcPath);
if (srcRoot == null)
{
throw new AVMNotFoundException("Directory Not Found: " + srcPath);
}
if (!srcRoot.isDirectory())
{
throw new AVMWrongTypeException("Not a directory: " + srcPath);
}
/**
* The destination directory exists - check is actually a directory
*/
if (!dstRoot.isDirectory())
{
throw new AVMWrongTypeException("Not a Directory: " + dstPath);
}
try
{
/**
* Recursivly copy
*/
fgLogger.debug("both src and dest exist, recursivly deploy");
final AVMNodeDescriptor dstParentNode = dstRoot;
RetryingTransactionCallback<Integer> copyContentsRecursivly = new RetryingTransactionCallback<Integer>()
{
public Integer execute() throws Throwable
{
deployDirectoryPush(srcVersion, srcRoot, dstParentNode, remote, matcher, dontDelete, dontDo, callbacks);
return new Integer(0);
}
};
trn.setMaxRetries(1);
trn.doInTransaction(copyContentsRecursivly, false, true);
fgLogger.debug("finished copying, snapshot remote");
remote.createSnapshot(storePath[0], "Deployment", "Post Deployment Snapshot.");
DeploymentEvent event = new DeploymentEvent(DeploymentEvent.Type.END,
new Pair<Integer, String>(version, srcPath),
dstPath);
processEvent(event, callbacks);
return;
}
catch (AVMException e)
{
fgLogger.debug("error during remote copy and snapshot");
try
{
if (snapshot != -1)
{
fgLogger.debug("Attempting to roll back ");
AVMSyncService syncService = getSyncService(hostName, port);
List<AVMDifference> diffs = syncService.compare(snapshot, dstPath, -1, dstPath, null);
syncService.update(diffs, null, false, false, true, true, "Aborted Deployment", "Aborted Deployment");
}
}
catch (Exception ee)
{
throw new AVMException("Failed to rollback to version " + snapshot + " on " + hostName, ee);
}
throw new AVMException("Deployment to " + hostName + " failed.", e);
}
}
catch (Exception e)
{
DeploymentEvent event = new DeploymentEvent(DeploymentEvent.Type.FAILED,
new Pair<Integer, String>(version, srcPath),
dstPath, e.getMessage());
processEvent(event, callbacks);
throw new AVMException("Deployment to " + hostName + " failed." + e.toString(), e);
}
finally
{
fgLogger.debug("ASR Finally block, Releasing ASR deployment ticket");
fTicketHolder.setTicket(null);
}
}
finally
{
fgLogger.debug("about to release lock");
lock.releaseLock();
}
}
/**
* Deploy all the children of corresponding directories. (ASR version)
* @param src The source directory.
* @param dst The destination directory.
* @param remote The AVMRemote instance.
* @param dontDelete Flag for not deleting.
* @param dontDo Flag for dry run.
*/
private void deployDirectoryPush(int version,
AVMNodeDescriptor src,
AVMNodeDescriptor dst,
AVMRemote remote,
NameMatcher matcher,
boolean dontDelete, boolean dontDo,
List<DeploymentCallback> callbacks)
{
if (src.getGuid().equals(dst.getGuid()))
{
return;
}
if (!dontDo && !dontDelete)
{
copyMetadata(version, src, dst, remote);
}
// Get the listing for the source.
SortedMap<String, AVMNodeDescriptor> srcList = fAVMService.getDirectoryListing(src);
// Get the listing for the destination.
SortedMap<String, AVMNodeDescriptor> dstList = remote.getDirectoryListing(dst);
// Strip out stale nodes.
for (Map.Entry<String, AVMNodeDescriptor> entry : srcList.entrySet())
{
String name = entry.getKey();
AVMNodeDescriptor srcNode = entry.getValue();
if (isStale(srcNode))
{
if (fgLogger.isDebugEnabled())
{
fgLogger.debug("Stale child found: " + srcNode);
}
srcList.remove(name);
}
}
for (Map.Entry<String, AVMNodeDescriptor> entry : srcList.entrySet())
{
String name = entry.getKey();
AVMNodeDescriptor srcNode = entry.getValue();
AVMNodeDescriptor dstNode = dstList.get(name);
if (!excluded(matcher, srcNode.getPath(), dstNode != null ? dstNode.getPath() : null))
{
if(isStale(srcNode))
{
fgLogger.debug("stale file not added" + srcNode);
continue;
}
deploySinglePush(version, srcNode, dst, dstNode, remote, matcher, dontDelete, dontDo, callbacks);
}
}
// Delete nodes that are missing in the source.
if (dontDelete)
{
return;
}
for (String name : dstList.keySet())
{
if (!srcList.containsKey(name))
{
Pair<Integer, String> source =
new Pair<Integer, String>(version, AVMNodeConverter.ExtendAVMPath(src.getPath(), name));
String destination = AVMNodeConverter.ExtendAVMPath(dst.getPath(), name);
if (!excluded(matcher, null, destination))
{
DeploymentEvent event =
new DeploymentEvent(DeploymentEvent.Type.DELETED,
source,
destination);
processEvent(event, callbacks);
if (dontDo)
{
continue;
}
remote.removeNode(dst.getPath(), name);
}
}
}
}
/**
* Push out a single node.
* @param src The source node.
* @param dstParent The destination parent.
* @param dst The destination node. May be null.
* @param remote The AVMRemote instance.
* @param dontDelete Flag for whether deletions should happen.
* @param dontDo Dry run flag.
*/
private void deploySinglePush(int version,
AVMNodeDescriptor src, AVMNodeDescriptor dstParent,
AVMNodeDescriptor dst, AVMRemote remote,
NameMatcher matcher,
boolean dontDelete, boolean dontDo,
List<DeploymentCallback> callbacks)
{
// Destination does not exist.
if (dst == null)
{
if (src.isDirectory())
{
// Recursively copy a source directory.
Pair<Integer, String> source =
new Pair<Integer, String>(version, src.getPath());
String destination = AVMNodeConverter.ExtendAVMPath(dstParent.getPath(), src.getName());
DeploymentEvent event = new DeploymentEvent(DeploymentEvent.Type.CREATED,
source,
destination);
processEvent(event, callbacks);
if (dontDo)
{
return;
}
copyDirectory(version, src, dstParent, remote, matcher, callbacks);
return;
}
// here when src is a file
Pair<Integer, String> source =
new Pair<Integer, String>(version, src.getPath());
String destination = AVMNodeConverter.ExtendAVMPath(dstParent.getPath(), src.getName());
DeploymentEvent event = new DeploymentEvent(DeploymentEvent.Type.CREATED,
source,
destination);
processEvent(event, callbacks);
if (dontDo)
{
return;
}
// Copy a source file.
OutputStream out = remote.createFile(dstParent.getPath(), src.getName());
try
{
InputStream in = fAVMService.getFileInputStream(src);
copyStream(in, out);
}
finally
{
if(out != null)
{
// whatever happens close stream
try
{
out.close();
}
catch (IOException e)
{
throw new AVMException("I/O Exception", e);
}
}
}
copyMetadata(version, src, remote.lookup(-1, dstParent.getPath() + '/' + src.getName()), remote);
return;
}
// Destination exists and is a directory.
if (src.isDirectory())
{
// If the destination is also a directory, recursively deploy.
if (dst.isDirectory())
{
deployDirectoryPush(version, src, dst, remote, matcher, dontDelete, dontDo, callbacks);
return;
}
Pair<Integer, String> source =
new Pair<Integer, String>(version, src.getPath());
String destination = dst.getPath();
DeploymentEvent event = new DeploymentEvent(DeploymentEvent.Type.CREATED,
source, destination);
processEvent(event, callbacks);
if (dontDo)
{
return;
}
// MER WHY IS THIS HERE ?
fgLogger.debug("Remove and recopy node :" + dstParent.getPath() + '/' + src.getName());
remote.removeNode(dstParent.getPath(), src.getName());
copyDirectory(version, src, dstParent, remote, matcher, callbacks);
return;
}
// Source exists and is a file.
if (dst.isFile())
{
// Destination is also a file. Overwrite if the GUIDS are different.
if (src.getGuid().equals(dst.getGuid()))
{
return;
}
Pair<Integer, String> source =
new Pair<Integer, String>(version, src.getPath());
String destination = dst.getPath();
DeploymentEvent event = new DeploymentEvent(DeploymentEvent.Type.UPDATED,
source,
destination);
processEvent(event, callbacks);
if (dontDo)
{
return;
}
OutputStream out = remote.getFileOutputStream(dst.getPath());
try
{
InputStream in = fAVMService.getFileInputStream(src);
copyStream(in, out);
}
finally
{
if(out != null)
{
// whatever happens close stream
try
{
out.close();
}
catch (IOException e)
{
throw new AVMException("I/O Exception", e);
}
}
}
copyMetadata(version, src, dst, remote);
return;
}
Pair<Integer, String> source =
new Pair<Integer, String>(version, src.getPath());
String destination = AVMNodeConverter.ExtendAVMPath(dstParent.getPath(), src.getName());
DeploymentEvent event = new DeploymentEvent(DeploymentEvent.Type.UPDATED,
source,
destination);
processEvent(event, callbacks);
if (dontDo)
{
return;
}
// Destination is a directory and the source is a file.
// Delete the destination directory and copy the file over.
remote.removeNode(dstParent.getPath(), dst.getName());
OutputStream out = remote.createFile(dstParent.getPath(), src.getName());
try
{
InputStream in = fAVMService.getFileInputStream(src);
copyStream(in, out);
}
finally
{
if(out != null)
{
// whatever happens close stream
try
{
out.close();
}
catch (IOException e)
{
throw new AVMException("I/O Exception", e);
}
}
}
copyMetadata(version, src, remote.lookup(-1, dstParent.getPath() + '/' + dst.getName()), remote);
}
/**
* Recursively copy a directory.
* @param src
* @param parent
* @param remote
*/
private void copyDirectory(int version, AVMNodeDescriptor src, AVMNodeDescriptor parent,
AVMRemote remote, NameMatcher matcher, List<DeploymentCallback>callbacks)
{
// Create the destination directory.
remote.createDirectory(parent.getPath(), src.getName());
AVMNodeDescriptor newParent = remote.lookup(-1, parent.getPath() + '/' + src.getName());
copyMetadata(version, src, newParent, remote);
SortedMap<String, AVMNodeDescriptor> list =
fAVMService.getDirectoryListing(src);
// For each child in the source directory.
for (AVMNodeDescriptor child : list.values())
{
if (!excluded(matcher, child.getPath(), null))
{
/**
* Temporary work around for staleness.
*/
if (isStale(child))
{
if (fgLogger.isDebugEnabled())
{
fgLogger.debug("Stale child found: " + child);
}
continue;
}
// If it's a file, copy it over and move on.
if (child.isFile())
{
DeploymentEvent event =
new DeploymentEvent(DeploymentEvent.Type.CREATED,
new Pair<Integer, String>(version, src.getPath() + '/' + child.getName()),
newParent.getPath() + '/' + child.getName());
processEvent(event, callbacks);
OutputStream out = remote.createFile(newParent.getPath(), child.getName());
try
{
InputStream in = fAVMService.getFileInputStream(child);
copyStream(in, out);
}
finally
{
if(out != null)
{
// whatever happens close stream
try
{
out.close();
}
catch (IOException e)
{
throw new AVMException("I/O Exception", e);
}
}
}
copyMetadata(version, child, remote.lookup(-1, newParent.getPath() + '/' + child.getName()), remote);
}
else
{
// is a directory
DeploymentEvent event =
new DeploymentEvent(DeploymentEvent.Type.CREATED,
new Pair<Integer, String>(version, src.getPath() + '/' + child.getName() ),
newParent.getPath() + '/' + child.getName());
processEvent(event, callbacks);
// Otherwise copy the child directory recursively.
copyDirectory(version, child, newParent, remote, matcher, callbacks);
}
}
}
}
/**
* Utility for copying from one stream to another.
*
* in is closed.
*
* out is not closed.
*
* @param in The input stream.
* @param out The output stream.
*/
private void copyStream(InputStream in, OutputStream out)
{
byte[] buff = new byte[8192];
int read = 0;
try
{
while ((read = in.read(buff)) != -1)
{
out.write(buff, 0, read);
}
in.close();
//out.flush();
//out.close();
}
catch (IOException e)
{
throw new AVMException("I/O Exception", e);
}
}
private void copyMetadata(int version, AVMNodeDescriptor src, AVMNodeDescriptor dst, AVMRemote remote)
{
Map<QName, PropertyValue> props = fAVMService.getNodeProperties(version, src.getPath());
remote.setNodeProperties(dst.getPath(), props);
Set<QName> aspects = fAVMService.getAspects(version, src.getPath());
for (QName aspect : aspects)
{
if (remote.hasAspect(-1, dst.getPath(), aspect))
{
continue;
}
remote.addAspect(dst.getPath(), aspect);
}
remote.setGuid(dst.getPath(), src.getGuid());
if (src.isFile())
{
ContentData contData = fAVMService.getContentDataForRead(version, src.getPath());
remote.setEncoding(dst.getPath(), contData.getEncoding());
remote.setMimeType(dst.getPath(), contData.getMimetype());
}
}
/**
* Utility to get an AVMRemote from a remote Alfresco Server.
* @param hostName
* @param port
* @param userName
* @param password
* @return
*/
private AVMRemote getRemote(String hostName, int port, String userName, String password)
{
try
{
RmiProxyFactoryBean authFactory = new RmiProxyFactoryBean();
authFactory.setRefreshStubOnConnectFailure(true);
authFactory.setServiceInterface(AuthenticationService.class);
authFactory.setServiceUrl("rmi://" + hostName + ":" + port + "/authentication");
authFactory.afterPropertiesSet();
AuthenticationService authService = (AuthenticationService)authFactory.getObject();
authService.authenticate(userName, password.toCharArray());
String ticket = authService.getCurrentTicket();
fTicketHolder.setTicket(ticket);
RmiProxyFactoryBean remoteFactory = new RmiProxyFactoryBean();
remoteFactory.setRefreshStubOnConnectFailure(true);
remoteFactory.setServiceInterface(AVMRemoteTransport.class);
remoteFactory.setServiceUrl("rmi://" + hostName + ":" + port + "/avm");
remoteFactory.afterPropertiesSet();
AVMRemoteTransport transport = (AVMRemoteTransport)remoteFactory.getObject();
AVMRemoteImpl remote = new AVMRemoteImpl();
remote.setAvmRemoteTransport(transport);
remote.setClientTicketHolder(fTicketHolder);
return remote;
}
catch (Exception e)
{
throw new AVMException("Could not Initialize Remote Connection to " + hostName, e);
}
}
/* (non-Javadoc)
* @see org.alfresco.service.cmr.avm.deploy.DeploymentService#getRemoteActionService(java.lang.String, int, java.lang.String, java.lang.String)
*/
public ActionService getRemoteActionService(String hostName, int port, String userName, String password)
{
try
{
RmiProxyFactoryBean authFactory = new RmiProxyFactoryBean();
authFactory.setRefreshStubOnConnectFailure(true);
authFactory.setServiceInterface(AuthenticationService.class);
authFactory.setServiceUrl("rmi://" + hostName + ":" + port + "/authentication");
authFactory.afterPropertiesSet();
AuthenticationService authService = (AuthenticationService)authFactory.getObject();
authService.authenticate(userName, password.toCharArray());
String ticket = authService.getCurrentTicket();
fTicketHolder.setTicket(ticket);
RmiProxyFactoryBean remoteFactory = new RmiProxyFactoryBean();
remoteFactory.setRefreshStubOnConnectFailure(true);
remoteFactory.setServiceInterface(ActionServiceTransport.class);
remoteFactory.setServiceUrl("rmi://" + hostName + ":" + port + "/action");
remoteFactory.afterPropertiesSet();
ActionServiceTransport transport = (ActionServiceTransport)remoteFactory.getObject();
ActionServiceRemote remote = new ActionServiceRemote();
remote.setActionServiceTransport(transport);
remote.setClientTicketHolder(fTicketHolder);
return remote;
}
catch (Exception e)
{
throw new AVMException("Could not Initialize Remote Connection to " + hostName, e);
}
}
/**
* Utility method to get the payload transformers for a named transport
*
* The transport adapters are sprung into the deploymentReceiverTransportAdapters property
*
* @return the transformers
*/
private List<DeploymentTransportOutputFilter> getTransformers(String transportName)
{
DeploymentReceiverTransportAdapter adapter = deploymentReceiverTransportAdapters.get(transportName);
if(adapter == null) {
// Adapter does not exist
fgLogger.error("Deployment Receiver Transport adapter does not exist for transport. Name: " + transportName);
throw new AVMException("Deployment Receiver Transport adapter does not exist for transport. Name: " + transportName);
}
List<DeploymentTransportOutputFilter> transformers = adapter.getTransformers();
return transformers;
}
/**
* Utility method to get a connection to a remote file system receiver (FSR)
*
* The transport adapters are sprung into the deploymentReceiverTransportAdapters property
* @param transportName the name of the adapter for the transport
* @param hostName the hostname or IP address to connect to
* @param port the port number
* @param version the version of the website to deploy
* @param srcPath the path of the website
*
* @return an implementation of the service
*/
private DeploymentReceiverService getDeploymentReceiverService(String transportName, String hostName, int port, int version, String srcPath)
{
DeploymentReceiverTransportAdapter adapter = deploymentReceiverTransportAdapters.get(transportName);
if(adapter == null) {
// Adapter does not exist
fgLogger.error("Deployment Receiver Transport adapter does not exist for transport. Name: " + transportName);
throw new AVMException("Deployment Receiver Transport adapter does not exist for transport. Name: " + transportName);
}
try
{
DeploymentReceiverTransport transport = adapter.getTransport(hostName, port, version, srcPath);
// Now decorate the transport with the service client
DeploymentReceiverServiceClient service = new DeploymentReceiverServiceClient();
service.setDeploymentReceiverTransport(transport);
return service;
}
catch (Exception e)
{
throw new AVMException("Could not connect to remote deployment receiver, transportName:" + transportName + ", hostName:" + hostName + ", port: " + port, e);
}
}
/**
* Utility to get the sync service for rolling back after a failed deployment.
* @param hostName The target machine.
* @param port The port.
* @return An AVMSyncService instance.
*/
private AVMSyncService getSyncService(String hostName, int port)
{
try
{
RmiProxyFactoryBean syncFactory = new RmiProxyFactoryBean();
syncFactory.setRefreshStubOnConnectFailure(true);
syncFactory.setServiceInterface(AVMSyncServiceTransport.class);
syncFactory.setServiceUrl("rmi://" + hostName + ":" + port + "/avmsync");
syncFactory.afterPropertiesSet();
AVMSyncServiceTransport syncServiceTransport = (AVMSyncServiceTransport)syncFactory.getObject();
AVMSyncServiceRemote remote = new AVMSyncServiceRemote();
remote.setAvmSyncServiceTransport(syncServiceTransport);
remote.setClientTicketHolder(fTicketHolder);
return remote;
}
catch (Exception e)
{
throw new AVMException("Could not roll back failed deployment to " + hostName, e);
}
}
/**
* Helper function to create a non existent destination.
* @param remote The AVMRemote instance.
* @param dstPath The destination path to create.
*/
private void createDestination(AVMRemote remote, String dstPath)
{
String[] storePath = dstPath.split(":");
String storeName = storePath[0];
String path = storePath[1];
AVMStoreDescriptor storeDesc = remote.getStore(storeName);
if (storeDesc == null)
{
remote.createStore(storeName);
}
SimplePath simpPath = new SimplePath(path);
if (simpPath.size() == 0)
{
return;
}
String prevPath = storeName + ":/";
for (int i = 0; i < simpPath.size(); i++)
{
String currPath = AVMNodeConverter.ExtendAVMPath(prevPath, simpPath.get(i));
AVMNodeDescriptor desc = remote.lookup(-1, currPath);
if (desc == null)
{
remote.createDirectory(prevPath, simpPath.get(i));
}
prevPath = currPath;
}
}
private Set<String>getAspects(AVMService avmService, AVMNodeDescriptor src)
{
Set<QName>aspects = avmService.getAspects(src);
Set<String>stringAspects = new HashSet<String>();
for (QName aspect : aspects)
{
stringAspects.add(aspect.toString());
}
return stringAspects;
}
private Map<String, Serializable> getProperties(AVMNodeDescriptor src, int version)
{
/**
* Get the AVM properties - which do not have any of the "syntetic" Node Service Values.
*/
Map<QName, PropertyValue> properties = fAVMService.getNodeProperties(src);
NodeRef nodeRef = AVMNodeConverter.ToNodeRef(version, src.getPath());
/**
* Get the properties in Node Service format
*/
Map<QName, Serializable> nodeProps = fAVMNodeService.getProperties(nodeRef);
Map<String, Serializable> retVal = new HashMap<String, Serializable>();
for(QName key : properties.keySet())
{
Serializable value = nodeProps.get(key);
retVal.put(key.toString(), value);
}
return retVal;
}
/**
* Deploy differences to a File System Receiver, FSR
*
* @param version snapshot version to deploy. If 0 then a new snapshot is created.
* @param srcPath
* @param adapterName
* @param hostName
* @param port
* @param userName
* @param password
* @param target
* @param matcher
* @param createDst Not implemented
* @param dontDelete Not implemented
* @param dontDo Not implemented
* @param callbacks Event callbacks when a deployment Starts, Ends, Adds, Deletes etc.
*
* @throws AVMException
*
* @see org.alfresco.service.cmr.avm.deploy.DeploymentService#deployDifferenceFS(int, java.lang.String, java.lang.String, int, java.lang.String, java.lang.String, java.lang.String, boolean, boolean)
*/
public void deployDifferenceFS(int version,
final String srcPath,
String adapterName,
String hostName,
int port,
String userName,
String password,
String target,
final NameMatcher matcher,
boolean createDst,
boolean dontDelete,
boolean dontDo,
List<DeploymentCallback> callbacks)
{
fgLogger.debug("deployDifferenceFS start");
/**
* Lock cluster for the remote target
*/
String lockStr = "deploy." + hostName + "." + port + "." + target;
QName lockQName = QName.createQName("{http://www.alfresco.org/deploymentService/1.0}" + lockStr);
final Lock lock = new Lock(lockQName);
lock.makeLock();
try
{
/**
* Cluster Lock held here
*/
if (fgLogger.isDebugEnabled())
{
Object[] objs = {version, srcPath, adapterName, hostName, port, target};
MessageFormat f = new MessageFormat("Deployment Lock Held: version {0}, srcPath {1}, adapterName {2}, hostName {3}, port {4}, target {5}");
fgLogger.debug(f.format(objs));
}
DeploymentReceiverService service = null;
List<DeploymentTransportOutputFilter>transformers = null;
String ticket = null;
String currentEffectiveUser = AuthenticationUtil.getRunAsUser();
try
{
// Kick off the event queue that will process deployment call-backs
final LinkedBlockingQueue<DeploymentEvent> eventQueue = new LinkedBlockingQueue<DeploymentEvent>();
EventQueueWorker eventQueueWorker = new EventQueueWorker(currentEffectiveUser, eventQueue, callbacks);
eventQueueWorker.setName(eventQueueWorker.getClass().getName());
eventQueueWorker.setPriority(Thread.currentThread().getPriority());
eventQueueWorker.start();
try
{
final String storeName = srcPath.substring(0, srcPath.indexOf(':'));
try {
if (version < 0)
{
RetryingTransactionHelper trn = trxService.getRetryingTransactionHelper();
RetryingTransactionCallback<Integer> localSnapshot = new RetryingTransactionCallback<Integer>()
{
public Integer execute() throws Throwable
{
int newVersion = fAVMService.createSnapshot(storeName, null, null).get(storeName);
return new Integer(newVersion);
}
};
version = trn.doInTransaction(localSnapshot, false, true).intValue();
fgLogger.debug("snapshot local created " + storeName + ", " + version);
}
transformers = getTransformers(adapterName);
service = getDeploymentReceiverService(adapterName, hostName, port, version, srcPath);
}
catch (Exception e)
{
// unable to get service
eventQueue.add(new DeploymentEvent(DeploymentEvent.Type.FAILED,
new Pair<Integer, String>(version, srcPath),
target, e.getMessage()));
throw e;
}
eventQueue.add(new DeploymentEvent(DeploymentEvent.Type.START,
new Pair<Integer, String>(version, srcPath),
target));
// Go parallel to reduce the problems of high network latency
final LinkedBlockingQueue<DeploymentWork> sendQueue = new LinkedBlockingQueue<DeploymentWork>();
final List<Exception> errors = Collections.synchronizedList(new ArrayList<Exception>());
SendQueueWorker[] workers = new SendQueueWorker[numberOfSendingThreads];
for(int i = 0; i < numberOfSendingThreads; i++)
{
workers[i] = new SendQueueWorker(currentEffectiveUser, service, fAVMService, trxService, errors, eventQueue, sendQueue, transformers);
workers[i].setName(workers[i].getClass().getName());
workers[i].setPriority(Thread.currentThread().getPriority());
}
for(SendQueueWorker sender : workers)
{
sender.start();
}
try
{
fgLogger.debug("calling begin");
DeploymentToken token = service.begin(target, storeName, version, userName, password.toCharArray());
ticket = token.getTicket();
lock.checkLock();
// run this in its own txn
final DeploymentReceiverService fservice = service;
final String fTicket = ticket;
final int fVersion = version;
RetryingTransactionCallback<Integer> pushFSR = new RetryingTransactionCallback<Integer>()
{
public Integer execute() throws Throwable
{
deployDirectoryPushFSR(fservice, fTicket, fVersion, srcPath, "/", matcher, eventQueue, sendQueue, errors, lock);
return 0;
}
};
RetryingTransactionHelper trn = trxService.getRetryingTransactionHelper();
trn.doInTransaction(pushFSR, false, true);
}
catch (Exception e)
{
errors.add(e);
}
catch (Throwable t)
{
errors.add(new AVMException("Unexpected Throwable", t));
}
finally
{
// clean up senders thread pool
fgLogger.debug("closing deployment workers");
for(SendQueueWorker sender : workers)
{
sender.stopMeWhenIdle();
}
for(SendQueueWorker sender : workers)
{
sender.join();
}
fgLogger.debug("deployment workers closed");
if (errors.size() <= 0 && ticket != null)
{
try
{
fgLogger.debug("no errors - prepare and commit");
lock.checkLock();
service.prepare(ticket);
lock.checkLock();
service.commit(ticket);
// no point checking the lock here - we have committed.
}
catch (Exception e)
{
errors.add(e);
}
}
if(errors.size() > 0)
{
fgLogger.debug("errors on deployment workers");
Exception firstError = errors.get(0);
eventQueue.add(new DeploymentEvent(DeploymentEvent.Type.FAILED,
new Pair<Integer, String>(version, srcPath),
target, firstError.getMessage()));
if (ticket != null)
{
try
{
service.abort(ticket);
}
catch (Exception ae)
{
// nothing we can do here
fgLogger.error("Unable to abort deployment. Error in exception handler", ae);
}
}
// yes there were errors, throw the first exception that was saved
MessageFormat f = new MessageFormat("Error during deployment srcPath: {0}, version:{1}, adapterName:{2}, hostName:{3}, port:{4}, error:{5}");
Object[] objs = { srcPath, version, adapterName, hostName, port, firstError };
throw new AVMException(f.format(objs), firstError);
}
} // end of finally block
// Success if we get here
eventQueue.add(new DeploymentEvent(DeploymentEvent.Type.END,
new Pair<Integer, String>(version, srcPath),
target));
fgLogger.debug("deployment completed successfully");
}
finally
{
// Now stutdown the event queue
fgLogger.debug("closing event queue");
eventQueueWorker.stopMeWhenIdle();
eventQueueWorker.join();
fgLogger.debug("event queue closed");
}
}
catch (Exception e)
{
// yes there were errors
MessageFormat f = new MessageFormat("Deployment exception, unable to deploy : srcPath:{0}, target:{1}, version:{2}, adapterName:{3}, hostName:{4}, port:{5}, error:{6}");
Object[] objs = { srcPath, target, version, adapterName, hostName, port, e };
throw new AVMException(f.format(objs), e);
}
}
finally
{
fgLogger.debug("At end of method - about to release lock");
lock.releaseLock();
}
} // End of deploy difference FS
private class ComparatorFileDescriptorCaseSensitive implements Comparator<FileDescriptor>
{
public int compare(FileDescriptor o1, FileDescriptor o2)
{
return o1.getName().compareTo(o2.getName());
}
}
private class ComparatorAVMNodeDescriptorCaseSensitive implements Comparator<AVMNodeDescriptor>
{
public int compare(AVMNodeDescriptor o1, AVMNodeDescriptor o2)
{
return o1.getName().compareTo(o2.getName());
}
}
private ComparatorFileDescriptorCaseSensitive FILE_DESCRIPTOR_CASE_SENSITIVE = new ComparatorFileDescriptorCaseSensitive();
private ComparatorAVMNodeDescriptorCaseSensitive AVM_DESCRIPTOR_CASE_SENSITIVE = new ComparatorAVMNodeDescriptorCaseSensitive();
/**
* deployDirectoryPush (FSR only)
*
* Compares the source and destination listings and updates report with update events required to make
* dest similar to src.
*
* @param service
* @param ticket
* @param report
* @param callbacks
* @param version
* @param srcPath
* @param dstPath
* @param matcher
*/
private void deployDirectoryPushFSR(DeploymentReceiverService service,
String ticket,
int version,
String srcPath,
String dstPath,
NameMatcher matcher,
BlockingQueue<DeploymentEvent> eventQueue,
BlockingQueue<DeploymentWork> sendQueue,
List<Exception> errors,
Lock lock)
{
Map<String, AVMNodeDescriptor> rawSrcListing = fAVMService.getDirectoryListing(version, srcPath);
List<FileDescriptor> rawDstListing = service.getListing(ticket, dstPath);
// Need to change from case insensitive order to case sensitive order
TreeSet<FileDescriptor> dstListing = new TreeSet<FileDescriptor>(FILE_DESCRIPTOR_CASE_SENSITIVE);
dstListing.addAll(rawDstListing);
TreeSet<AVMNodeDescriptor> srcListing = new TreeSet<AVMNodeDescriptor>(AVM_DESCRIPTOR_CASE_SENSITIVE);
srcListing.addAll(rawSrcListing.values());
Iterator<FileDescriptor> dstIter = dstListing.iterator();
Iterator<AVMNodeDescriptor> srcIter = srcListing.iterator();
lock.checkLock();
// Here with two sorted directory listings
AVMNodeDescriptor src = null;
FileDescriptor dst = null;
// Step through both directory listings
while ((srcIter.hasNext() || dstIter.hasNext() || src != null || dst != null) && errors.size() <= 0)
{
if (src == null)
{
if (srcIter.hasNext())
{
src = srcIter.next();
/**
* Temporary check for stale assets
*
* Correct fix would be to remove stale files from the snapshot.
* Code becomes obsolete once stale files are not part of the snapshot.
*/
if (isStale(src))
{
if (fgLogger.isDebugEnabled())
{
fgLogger.debug("Stale child found: " + src);
}
src = null;
continue;
}
}
}
if (dst == null)
{
if (dstIter.hasNext())
{
dst = dstIter.next();
}
}
if (fgLogger.isDebugEnabled())
{
fgLogger.debug("comparing src:" + src + " dst:"+ dst);
}
lock.checkLock();
// This means no entry on src so delete what is on dst.
if (src == null)
{
String newDstPath = extendPath(dstPath, dst.getName());
if (!excluded(matcher, null, newDstPath))
{
sendQueue.add(new DeploymentWork(new DeploymentEvent(DeploymentEvent.Type.DELETED,
new Pair<Integer, String>(version, extendPath(srcPath, dst.getName())),
newDstPath), ticket));
}
dst = null;
continue;
}
// Nothing on the destination so copy over.
if (dst == null)
{
if (!excluded(matcher, src.getPath(), null))
{
createOnFSR(service, ticket, version, src, dstPath, matcher, sendQueue);
}
src = null;
continue;
}
// Here with src and dst containing something
int diff = src.getName().compareTo(dst.getName());
if (diff < 0)
{
// src is less than dst - must be new content in src
if (!excluded(matcher, src.getPath(), null))
{
createOnFSR(service, ticket, version, src, dstPath, matcher, sendQueue);
}
src = null;
continue;
}
if (diff == 0)
{
/**
* src and dst have same file name and GUID - nothing to do
*/
if (src.getGuid().equals(dst.getGUID()))
{
src = null;
dst = null;
continue;
}
/**
* src and dst are different and src is a file
*/
if (src.isFile())
{
// this is an update to a file
String extendedPath = extendPath(dstPath, dst.getName());
if (!excluded(matcher, src.getPath(), extendedPath))
{
// Work in progress
sendQueue.add(new DeploymentWork(
new DeploymentEvent(DeploymentEvent.Type.UPDATED,
new Pair<Integer, String>(version, src.getPath()),
extendedPath), ticket, src, version));
}
src = null;
dst = null;
continue;
}
/**
* src and dst are different and src is a directory
*/
if (dst.getType() == FileType.DIR)
{
String extendedPath = extendPath(dstPath, dst.getName());
Set<String>stringAspects = getAspects(fAVMService, src);
Map<String, Serializable> stringProperties = getProperties(src, version);
/**
* Update the directory before any children
*/
service.updateDirectory(ticket, extendedPath, src.getGuid(), stringAspects, stringProperties);
if (!excluded(matcher, src.getPath(), extendedPath))
{
deployDirectoryPushFSR(service, ticket, version, src.getPath(), extendedPath, matcher, eventQueue, sendQueue, errors, lock);
}
src = null;
dst = null;
continue;
}
if (!excluded(matcher, src.getPath(), null))
{
createOnFSR(service, ticket, version, src, dstPath, matcher, sendQueue);
}
src = null;
dst = null;
continue;
}
/**
* diff > 0
* Destination is missing in source, delete it.
*/
String newDstPath = extendPath(dstPath, dst.getName());
sendQueue.add(new DeploymentWork(new DeploymentEvent(DeploymentEvent.Type.DELETED,
new Pair<Integer, String>(version, extendPath(srcPath, dst.getName())),
newDstPath), ticket));
//
dst = null;
}
}
/**
* Copy a file or directory to an empty destination on an FSR
* @param service
* @param ticket
* @param report
* @param callback
* @param version
* @param src
* @param parentPath
*/
private void createOnFSR(DeploymentReceiverService service,
String ticket,
int version,
AVMNodeDescriptor src,
String parentPath,
NameMatcher matcher,
BlockingQueue<DeploymentWork> sendQueue)
{
String dstPath = extendPath(parentPath, src.getName());
// Need to queue the request to copy file or dir to remote.
sendQueue.add(new DeploymentWork(
new DeploymentEvent(DeploymentEvent.Type.CREATED,
new Pair<Integer, String>(version, src.getPath()),
dstPath), ticket, src, version));
if (src.isFile())
{
return;
}
// here if src is a directory.
// Need to create directories in controlling thread since it needs to be created
// BEFORE any children are written
Set<String>stringAspects = getAspects(fAVMService, src);
Map<String, Serializable> stringProperties = getProperties(src, version);
service.createDirectory(ticket, dstPath, src.getGuid(), stringAspects, stringProperties);
// now copy the children over
Map<String, AVMNodeDescriptor> listing = fAVMService.getDirectoryListing(src);
for (AVMNodeDescriptor child : listing.values())
{
if (!excluded(matcher, child.getPath(), null))
{
if (isStale(child))
{
if (fgLogger.isDebugEnabled())
{
fgLogger.debug("Stale child found: " + child);
}
continue;
}
createOnFSR(service, ticket, version, child, dstPath, matcher, sendQueue);
}
}
}
private void processEvent(DeploymentEvent event, List<DeploymentCallback> callbacks)
{
if (fgLogger.isDebugEnabled())
{
fgLogger.debug(event);
}
if (callbacks != null)
{
for (DeploymentCallback callback : callbacks)
{
callback.eventOccurred(event);
}
}
}
/**
* Extend a path.
* @param path
* @param name
* @return
*/
private String extendPath(String path, String name)
{
if (path.endsWith("/"))
{
return path + name;
}
return path + '/' + name;
}
/**
* Returns true if either srcPath or dstPath are matched by matcher.
* @param matcher
* @param srcPath
* @param dstPath
* @return
*/
private boolean excluded(NameMatcher matcher, String srcPath, String dstPath)
{
return matcher != null && ((srcPath != null && matcher.matches(srcPath)) || (dstPath != null && matcher.matches(dstPath)));
}
private Map<String, DeploymentReceiverTransportAdapter> deploymentReceiverTransportAdapters;
/**
* The deployment transport adapters provide the factories used to connect to a remote file system receiver.
*/
public void setDeploymentReceiverTransportAdapters(Map<String, DeploymentReceiverTransportAdapter> adapters) {
this.deploymentReceiverTransportAdapters=adapters;
}
public Map<String, DeploymentReceiverTransportAdapter> getDeploymentReceiverTransportAdapters() {
return this.deploymentReceiverTransportAdapters;
}
public Set<String> getAdapterNames()
{
if(deploymentReceiverTransportAdapters != null) {
return(deploymentReceiverTransportAdapters.keySet());
}
else
{
Set<String> ret = new HashSet<String>(1);
ret.add("default");
return ret;
}
}
public void setNumberOfSendingThreads(int numberOfSendingThreads) {
this.numberOfSendingThreads = numberOfSendingThreads;
}
public int getNumberOfSendingThreads() {
return numberOfSendingThreads;
}
public void setJobLockService(JobLockService jobLockService) {
this.jobLockService = jobLockService;
}
public JobLockService getJobLockService() {
return jobLockService;
}
public void setTargetLockTimeToLive(long targetLockTimeToLive) {
this.targetLockTimeToLive = targetLockTimeToLive;
}
public long getTargetLockTimeToLive() {
return targetLockTimeToLive;
}
public void setTargetLockRetryWait(long targetLockRetryWait) {
this.targetLockRetryWait = targetLockRetryWait;
}
public long getTargetLockRetryWait() {
return targetLockRetryWait;
}
public void setTargetLockRetryCount(int targetLockRetryCount) {
this.targetLockRetryCount = targetLockRetryCount;
}
public int getTargetLockRetryCount() {
return targetLockRetryCount;
}
public void setAvmNodeService(AVMNodeService fAVMNodeService) {
this.fAVMNodeService = fAVMNodeService;
}
public AVMNodeService getAvmNodeService() {
return fAVMNodeService;
}
public void setOutputBufferSize(int outputBufferSize) {
this.outputBufferSize = outputBufferSize;
}
public int getOutputBufferSize() {
return outputBufferSize;
}
/**
* This thread processes the event queue to do the callbacks
* @author mrogers
*
*/
private class EventQueueWorker extends Thread
{
private BlockingQueue<DeploymentEvent> eventQueue;
private List<DeploymentCallback> callbacks;
private String userName;
private boolean stopMe = false;
EventQueueWorker(String userName, BlockingQueue<DeploymentEvent> eventQueue, List<DeploymentCallback> callbacks)
{
this.eventQueue = eventQueue;
this.callbacks = callbacks;
this.userName = userName;
}
public void run()
{
AuthenticationUtil.setFullyAuthenticatedUser(userName);
while (true)
{
DeploymentEvent event = null;
try {
event = eventQueue.poll(3, TimeUnit.SECONDS);
} catch (InterruptedException e1) {
fgLogger.debug("Interrupted ", e1);
}
if(event == null)
{
if(stopMe)
{
fgLogger.debug("Event Queue Closing Normally");
break;
}
continue;
}
if (fgLogger.isDebugEnabled())
{
fgLogger.debug(event);
}
if (callbacks != null)
{
for (DeploymentCallback callback : callbacks)
{
callback.eventOccurred(event);
}
}
}
}
public void stopMeWhenIdle()
{
stopMe = true;
}
}
/**
* Inner Class to Decorate the jobLockService to
* add control over the refreshLock behaviour.
*
* Deployment service calls (On deployment main thread)
* makeLock and releaseLock around the deployment.
* periodically calls checkLock as it does its work.
* checkLock can throw an exception if the business process has timed out.
*
* isActive and lockReleased called by Job Lock Thread
*/
private class Lock implements JobLockRefreshCallback
{
/**
* The name of the lock - unique for each target
*/
QName lockQName;
/**
* The unique token for this lock instance.
*/
String lockToken;
/**
* Is the lock active ?
*/
boolean active = false;
/**
* When did we last check whether the lock is active
*/
Date lastActive = new Date();
public Lock(QName lockQName)
{
this.lockQName = lockQName;
}
/**
* Make the lock - called on main deployment thread
*
* @throws LockAquisitionException
*/
public void makeLock()
{
if(fgLogger.isDebugEnabled())
{
fgLogger.debug("target lock refresh time :" + getTargetLockRefreshTime() + "targetLockRetryWait:" + targetLockRetryWait + "targetLockRetryCount:" + targetLockRetryCount);
}
lockToken = jobLockService.getLock(lockQName, targetLockRefreshTime, targetLockRetryWait, targetLockRetryCount);
synchronized(this)
{
active = true;
}
if (fgLogger.isDebugEnabled())
{
fgLogger.debug("lock taken:" + lockQName);
}
// We may have taken so long to begin that we have already timed out !
checkLock();
fgLogger.debug("register lock callback, target lock refresh time :" + getTargetLockRefreshTime());
jobLockService.refreshLock(lockToken, lockQName, getTargetLockRefreshTime(), this);
fgLogger.debug("callback registered");
}
/**
* Refresh the lock - called as the business process progresses.
*
* Called on main deployment thread.
* @throws AVMException (Lock timeout)
*/
public void checkLock()
{
// Do I need to sync this?
if(active)
{
Date now = new Date();
if(now.getTime() > lastActive.getTime() + targetLockTimeToLive)
{
// lock time to live has expired.
MessageFormat f = new MessageFormat("Deployment Lock timeout, lock time to live exceeded, timeout:{0}mS time since last activity:{1}mS");
Object[] objs = {new Long(targetLockTimeToLive), new Long(now.getTime() - lastActive.getTime()) };
throw new AVMException(f.format(objs));
}
// Update lastActive to 1S boundary
if(now.getTime() > lastActive.getTime() + 1000)
{
lastActive = new Date();
fgLogger.debug("lastActive:" + lastActive);
}
}
else
{
// lock not active. Has been switched off by Job Lock Service.
MessageFormat f = new MessageFormat("Lock timeout, lock not active");
Object[] objs = { };
throw new AVMException(f.format(objs));
}
}
/**
* Release the lock
*
* Called on main deployment thread
*/
public void releaseLock()
{
if(fgLogger.isDebugEnabled())
{
fgLogger.debug("deployment service about to releaseLock : " + lockQName);
}
if(active)
{
jobLockService.releaseLock(lockToken, lockQName);
}
fgLogger.debug("setting active = false" + lockQName);
// may need to sync this
synchronized(this)
{
active = false;
}
}
/**
* Job Lock Callback
*
* Callback from the job lock service. Is the deployment active?
*/
@Override
public boolean isActive()
{
Date now = new Date();
synchronized(this)
{
if(now.getTime() > lastActive.getTime() + targetLockTimeToLive)
{
active = false;
}
// may need to sync active flag
if(fgLogger.isDebugEnabled())
{
fgLogger.debug("deployment service callback active: " + active);
}
return active;
}
}
/**
* Job Lock Callback.
*/
@Override
public void lockReleased()
{
fgLogger.debug("deployment service: lock released callback");
synchronized(this)
{
active = false;
}
}
}
/**
* This thread processes the send queue
* @author mrogers
*
*/
private class SendQueueWorker extends Thread
{
private BlockingQueue<DeploymentEvent> eventQueue;
private BlockingQueue<DeploymentWork> sendQueue;
private DeploymentReceiverService service;
private String userName;
private AVMService avmService;
private TransactionService trxService;
List<Exception> errors;
List<DeploymentTransportOutputFilter> transformers;
private boolean stopMe = false;
SendQueueWorker(String userName,
DeploymentReceiverService service,
AVMService avmService,
TransactionService trxService,
List<Exception> errors,
BlockingQueue<DeploymentEvent> eventQueue,
BlockingQueue<DeploymentWork> sendQueue,
List<DeploymentTransportOutputFilter> transformers
)
{
this.eventQueue = eventQueue;
this.sendQueue = sendQueue;
this.service = service;
this.avmService = avmService;
this.trxService = trxService;
this.errors = errors;
this.transformers = transformers;
this.userName = userName;
}
public void run()
{
AuthenticationUtil.setFullyAuthenticatedUser(userName);
while (errors.size() <= 0)
{
DeploymentWork work = null;
try {
work = sendQueue.poll(3, TimeUnit.SECONDS);
} catch (InterruptedException e1) {
fgLogger.debug("Interrupted ", e1);
continue;
}
if(work == null)
{
if(stopMe)
{
fgLogger.debug("Send Queue Worker Closing Normally");
eventQueue = null;
sendQueue = null;
service = null;
errors = null;
break;
}
}
if(work != null)
{
DeploymentEvent event = work.getEvent();
String ticket = work.getTicket();
try
{
if(event.getType().equals(DeploymentEvent.Type.DELETED))
{
service.delete(ticket, event.getDestination());
}
else if (event.getType().equals(DeploymentEvent.Type.CREATED))
{
AVMNodeDescriptor src = work.getSrc();
if(src.isFile())
{
copyFileToFSR(src, true, work.getVersion(), event.getDestination(), ticket);
}
else
{
// Do nothing. mkdir done on main thread.
//makeDirectoryOnFSR(src, event.getDestination(), ticket);
}
}
else if (event.getType().equals(DeploymentEvent.Type.UPDATED))
{
copyFileToFSR(work.getSrc(), false, work.getVersion(), event.getDestination(), ticket);
}
// success, now put the event onto the event queue
eventQueue.add(event);
}
catch (Exception e)
{
errors.add(e);
}
}
}
fgLogger.debug("Send Queue Worker finished");
}
public void stopMeWhenIdle()
{
stopMe = true;
}
/**
* Create or update a single file on a remote FSR.
* @param ticket
* @param src which file to copy
* @param dstPath where to copy the file
*/
private void copyFileToFSR(
final AVMNodeDescriptor src,
final boolean create,
final int version,
final String dstPath,
final String ticket)
{
try
{
// Perform copy within 'read only' transaction
RetryingTransactionHelper trx = trxService.getRetryingTransactionHelper();
trx.setMaxRetries(1);
trx.doInTransaction(new RetryingTransactionCallback<Boolean>()
{
public Boolean execute() throws Exception
{
ContentData data = avmService.getContentDataForRead(src);
InputStream in = avmService.getFileInputStream(src);
String encoding = data.getEncoding();
String mimeType = data.getMimetype();
Set<String>stringAspects = getAspects(avmService, src);
Map<String, Serializable> stringProperties = getProperties(src, version);
OutputStream out = service.send(ticket, create, dstPath, src.getGuid(), encoding, mimeType, stringAspects, stringProperties);
try
{
// Buffer the output, we don't want to send lots of small packets
out = new BufferedOutputStream(out, outputBufferSize);
// Call content transformers here to transform from local to network format
if(transformers != null && transformers.size() > 0) {
// yes we have pay-load transformers
for(DeploymentTransportOutputFilter transformer : transformers)
{
out = transformer.addFilter(out, src.getPath(), encoding, mimeType);
}
}
copyStream(in, out);
}
finally
{
// whatever happens close the output stream
if(out != null)
{
out.close();
out = null;
}
}
return true;
}
}, true);
}
catch (Exception e)
{
fgLogger.debug("Failed to copy dstPath:" + dstPath , e);
// throw first exception - this is the root of the problem.
throw new AVMException("Failed to copy filename:" + dstPath, e);
}
}
}
private boolean isStale(AVMNodeDescriptor avmRef)
{
// note: currently specific to WCM use-cases, eg. ETHREEOH-2758
if ((avmRef.isLayeredDirectory() && avmRef.isPrimary()) || avmRef.isLayeredFile())
{
AVMNodeDescriptor srcNode = avmRef;
while ((srcNode.isLayeredDirectory() && srcNode.isPrimary()) || srcNode.isLayeredFile())
{
AVMNodeDescriptor targetNode = fAVMService.lookup(srcNode.getIndirectionVersion(), srcNode.getIndirection());
if (targetNode == null)
{
if (srcNode.isLayeredFile() ||
(srcNode.isLayeredDirectory() &&
(! srcNode.getOpacity()) &&
fAVMService.getDirectoryListingDirect(srcNode, false).isEmpty()))
{
// The target node is missing
return true;
}
else
{
// unbacked layered dir - however opaque or not directly empty
return false;
}
}
srcNode = targetNode;
}
}
return false;
}
public void setTargetLockRefreshTime(long targetLockRefreshTime)
{
this.targetLockRefreshTime = targetLockRefreshTime;
}
/**
* How long to keep a lock before refreshing it?
* <p>
* Short time-out, typically a minute.
* @return the time in mS for how long to keep the lock.
*/
public long getTargetLockRefreshTime()
{
return targetLockRefreshTime;
}
}