Dave Ward 556377ea3b Merged V4.0-BUG-FIX to HEAD
34474: ALF-13169 Tomcat fails to shutdown
   - fix non daemon Timer's
   34475: Part 1: Fix for ALF-13244 SOLR Multi-threaded tracking is required for performance - simultaneous document transformations
   - multi-threaded transaction and node tracking (off by default at the moment)
   - fix index/repo sync check failure if ACLs have been indexed but no transactions
   - minimise data sent back from query responses (not all stored fields)
   - added SOLR side config for HTTPClient pooling, cache sizing and tracker configuration
   - fixed SOLR incremental cache update for merges that end with all deletions in the old index
   - fixed unclosed stream in SolrKeyResourceLoader
   34478: ALF-13050 - CIFS: Disabling account is not respected
   Also contains major rework of logging and exception handling.
   34499: Fix for ALF-13150
   34526: Fix for ALF-13288
   34530: Minor CSS tweaks after changes for ALF-11991
   34539: ALF-13176 - Implement Word for Mac 2011 Cifs Shuffle.
   34541: ALF-13244 SOLR Multi-threaded tracking is required for performance - simultaneous document transformations
   - multi-threaded ACL tracking
   - multi-threaded statistics and reporting control
   - nodes that can not be indexed have an error record added to the index and do not block indexing the transaction (nodes unindexed due to exceptions can be found by ID query and the exception stored in the index)
   - nodes that are not-indexed have a minimal record added to the index for index consistency checking (unindexed nodes can be found by ID query)
   34544: Add support for CIFS Level II shared oplocks. ALF-13138, ALF-13110.
   Fixed CIFS open for attributes only access preventing oplock on the following file open.
   Fixed reporting serialized copies of file access tokens as leaked.
   34576: ALF-12767 - CIFS TextEdit - File has been modified outside TextEdit
   34577: incorrectly checked in copy of network-protocol-context.xml
   34580: ALF-13215: Ensure that permissions for everyone cannot be upgraded on moderated or private site. Fixed inconsistency between permissions shown in properties and in dialog
   34582: ALF-13332: Updated modifier link for correct profile
   34609: ALF-12740: Update to previous fix (only apply to IE8 and below)
   34623: ALF-12767 - CIFS TextEdit - File has been modified outside TextEdit
   34636: Fix for ALF-13365 SOLR: Recently modified docs dashlet sorts incorrectly
   - respect short property names on sort requests @cm:created and not require the full @{uri...}created
   34659: ALF-2550 - added enterprise repo config files.
   34715: Fix for __ShowDetails desktop action returned URL is truncated if hostname too long. ALF-13202.
   34726: ALF-13293: Webdav: Version history lost after editing content in Finder
   34738: ALF-7883: WebDAV: support HEAD method for folder
   - Fix by Pavel
   34743: Fix for ALF-13244 SOLR Multi-threaded tracking is required for performance - simultaneous document transformations
   - batch fetch for nodes in transaction, acls in sets, and acls and readers
   - config for batch fetching
   - Better reporting for ACL set indexing
   34747: ALF-13262: adding missing indexes for new schema's (activiti-schema create) + schema patch for existing schema
   34817: Merged V4.0 to V4.0-BUG-FIX
      34493: SPANISH: translation updates based on EN r34103
      34498: Fixed ALF-12031: WCM: Content cannot be expired: avmExpiredContentTrigger is missing
       - Side-effect of ALF-11644: AVM cleanup jobs run when WCM is not installed
       - WORKAROUND: Get file 'root\projects\installer\wcm-bootstrap-context.xml' and use that
      34525: Fix for ALF-13210:
        - removed "unsupported" from bulk filesystem import web pages
      34531: Fix for ALF-13117 and ALF-13273
      34549: Merged BRANCHES/DEV/BELARUS/HEAD-2012_03_15 to BRANCHES/V4.0:
         34528: ALF-12874:
      34552: ALF-13322: Fixed doc lib reload loop caused by "#" in folder name
      34553: ALF-13311: Ensure images can be linked in TinyMCE create HTML content editor
      34556: Minor: removed unused code
      34557: Merged DEV to V4.0
         34537: ALF-13035: Add "START WITH" parameter to IDENTITY field.
                ALF-13034: Add "optional" parameter for statement that drops index that was generated automatically.
      34567: ALF-11047: Ensure that Explorer linked files and folders (from outside of sites) display correctly
      34578: Fixes: ALF-11744: Dates rendered with the form service date control are rendered on the server, so show server time.
         - I've added the timezone to the display format and the ISO8601 date to the as an attribute on the HTML element to allow client side parsing
         - Adds client side parsing on the Doc Details page, so times are shown in the timezone of the user's browser.
      34583: GERMAN: Translation update, based on EN r34103, Fixes: ALF-13075,
      34584: FRENCH: Translation update based on EN r34103, Fixes: ALF-13002, ALF-13003, ALF-13020
      34585: ITALIAN: Translation update based on EN r34103
      34586: JAPANESE: Translation update based on EN r34103
      34587: DUTCH: Translation update based on EN r34103, Fixes: ALF-12575.
      34626: Fixes: ALF-13375 - Date rendering bug in search results
      34630: Further fix for ALF-13375 that modifies Alfresco.util.formatDate's ISO8601 support for backward compatibility (e.g. passing in non ISO strings).
      34635: ALF-12061: Mac support: Document Connection always throws an error
      - Case sensitivity fix by Pavel
      34653: ALF-12308, ALF-12309, ALF-12554: Stack specific script errors
      34655: Fix for ALF-12723 CMIS: Over-riding cm:autoVersionOnUpdateProps in custom model prevents startup
      34656: Merged HEAD to BRANCHES/V4.0:
         34654: Fixes: ALF-13389: Old element id used when setting event end date.
      34657: Translation updates for all languages except JA.
      34660: Fix to license driven config files to remove erroneous characters
      34669: Merged DEV to V4.0
         34663: ALF-12242: User activation issue
            InviteHelper.acceptNominatedInvitation() method was changed to enable user account in any case(no matter was it enabled/disabled before)
      34681: Merged DEV/THEMIS2 to V4.0
         34472: Document List Customization Refactor
            - SLingshotSiteModuleEvalutaor now has new <applyForNonSite> param that defaults to false for backward compability 
            - Slingshot extension points, surf-doclist.get now uses 2 spring beans:
               * "resolver.doclib.doclistDataUrl" to get the repo doclist data url 
               * "resolver.doclib.actionGroup" to get each item/nodes action group id 
      34692: Fix for ALF-12715 - Incorrect SPP working (mimetype not set on document stored via ADM Remote Store API)
      34708: ALF-13239: Merged V3.4-BUG-FIX (3.4.9) to V4.0 (4.0.1)
         34707: ALF-13239 Share rule to convert to PNG fails on JPG images
            - Issue was showing up in 4.0.1 as a change was made for iPad that introduced an imageOptions.isAutoOrient()
              setting. This forced a concatenation of null with " -auto-orient". However there are also crop and resize
              options that could also do this even in 3.4
              Setting the commandOptions String to "" when null, is fine as this is how property value nulls are handled
              later anyway.
      34718: JAPANESE: Localisation of Company specific contact information & addition of timezone to form control.
      34719: FRENCH: File consistency tweak.
      34746: ALF-12903: Create HTML content fix
      34754: Merged PATCHES/V4.0.0 to V4.0
         34750: Reinstate ${version.label} into version.number property
      34810: Merged DEV to V4.0 (with corrections)
         34807: ALF-13290 : Mac Support: Error appears after collaborator saves changes to the document
            deleteFailedThumbnailChildren method should be run as system user as it may fails with AccesssDenied if collaborator updates document
   34876: Fix fo ALF-13503 Add SOLR client API tests to the SystemBuildTest project
   - SOLR API tests run embedded with SSL
   34984: ALF-13109 - Correction to NTIOCtl.FsCtlCreateOrGetObjectId
   35009: Merged BRANCHES/DEV/V3.4-BUG-FIX to BRANCHES/DEV/V4.0-BUG-FIX:
      35008: Fix for ALF-12817. Fixed as suggested - new method remove().
   35031: Fix for ALF-12309
   35032: Fix fo ALF-13535 using CMIS, on-disk tickets cache can grow unbounded
   - expire tickets based on inactivity by default
   - added job to clean up expired tickets
   - all are configurable
   35033: Fix fo ALF-13535 using CMIS, on-disk tickets cache can grow unbounded
   - avoid NPE for null tickets
   35037: Fix for ALF-13505 SOLR tracking readers does not encode all uids correctly
   - fixed reader encoding
   35049: ALF-13384 - Saving large Word (mac 2011) document via CIFS fails in Mac OS X Lion
   35053: Merged V4.0 (V4.0.1) to V4.0-BUG-FIX (4.0.2)
      34844: Merged V3.4-BUG-FIX (3.4.9) to V4.0 (4.0.1)
         34843: ALF-5830 show_audit.ftl template doesn't work anymore
            - Removed L10n messages that are no longer used (should have been removed in 3.4.6 when this issue was fixed)
      34847: Merged HEAD to BRANCHES/V4.0:
         34804: Fixes: ALF-13309: Issue with over zealous HTML escaping with truncated descriptions in the Calendar Agenda view.
      34861: ALF-13497: Merged PATCHES/V4.0.0 to V4.0
         34813: ALF-13115: No feedback is given to the user when Approve/Reject is clicked for a task when they followed a link to the task in an email.
            - Fix by Pavel, reviewed by Kev
            - Now they get a confirmation message followed by a redirect to their dashboard
      34862: Fix for ALF-10823 "allowGuestLogin=false" and Share then fills the alfresco error log with "Guest authentication not supported"
      Fix for ALF-12678 Errors in log on startup (ts.alfresco.com 4.0)
       - improved handling of 500 errors relating to GuestAuthNotSupported when alfresco.authentication.allowGuestLogin=false
      34867: Merged DEV to V4.0
         34565: ALF-13074: JBPM workflow definitions are not resilient to missing model definitions
            WARN messages have been added if JBPM workflow definitions cannot be loaded in the model definitions.
         34855: ALF-13074: JBPM workflow definitions are not resilient to missing model definitions
            Reimplemented to handle all exceptions during constructing WorkflowInstances WorkflowTasks and WorkflowDefinitions.
         34859: ALF-13074: JBPM workflow definitions are not resilient to missing model definitions
            Logger messages was changed to correspond the logger pattern.
      34893: Translation updates for DE and ES.
      34894: Fixes: ALF-13518; Updates Calendar event object's URL to work out of context.
      34896: FRENCH: Translates new strings.
      34915: Merged DEV to V4.0
         34912: ALF-13267: There should not be a web-client-config-custom.xml in alfresco.war
            Move "modules\quickr\config\alfresco\extension\web-client-config-custom.xml" to "modules\quickr\config\alfresco\module\org.alfresco.module.quickr\ui\web-client-custom.xml".
         34913: ALF-13267: There should not be a web-client-config-custom.xml in alfresco.war
            Delete "modules\quickr\config\alfresco\extension\web-client-config-custom.xml".
      34916: ALF-13267: Merged V3.4 to V4.0 (and reversed previous duplicate fix)
         24828: Merged BRANCHES/DEV/BELARUS/V3.4-2011_01_13 to BRANCHES/V3.4:
            24824: ALF-6361: web-client-config-custom.xml doesn't work in /alfresco/tomcat/shared/classes/alfresco/extension
      34929: ALF-12242: Issues activating users when more than one member in the authentication chain
      - Correction to fix that caused regressions ALF-13494, ALF-13498
      - Need to check for the mutability of a user's authentication before trying to enable it
      - Also chaining of the authentication enabled attribute should assume true until false found, not the other way around
      34930: ALF-12242: Reverted change to this class as it wasn't necessary and wouldn't work!
      34932: ALF-13453: Enable XMLConstants.FEATURE_SECURE_PROCESSING feature on Transformer Factory to prevent remote code execution
      - Now SecureTransformerFactory should be used as a standard
      34965: Merged PATCHES/V4.0.0 to V4.0
         34959: ALF-13550: Fix for ALF-13546 SOLR tracking fails for nodes with content and no auditable aspect - NPE as there is no last modification date to use
         34960: ALF-13551: Merged BRANCHES/DEV/V4.0-BUG-FIX to PATCHES\V4.0.0
         - fix for ALF-13544 When SOLR encounters an error indexing a document, subsequent indexing does not occur
             34541: ALF-13244 SOLR Multi-threaded tracking is required for performance - simultaneous document transformations
                    - nodes that can not be indexed have an error record added to the index and do not block indexing the transaction (nodes unindexed due to exceptions can be found by ID query and the exception stored in the index)
                    - nodes that are not-indexed have a minimal record added to the index for index consistency checking (unindexed nodes can be found by ID query)
      34968: ALF-13453: Reversed XSLTProcessor and XSLTRenderingEngine changes for now as they break http://wiki.alfresco.com/wiki/WCM_Forms_Rendering and model handling via bsf extensions. A more sophisticated approach is required. See bug for more info.
      34972: ALF-13340: Upgrade postgres JDBC driver to tested/supported version!
      34997: ALF-13453, ALF-13565: Fully reverted revision 34932 as it prevents startup on Weblogic
      34998: Merged V4.0-BUG-FIX to V4.0
         34992: DUTCH: translation updates based on EN r34861
         34993: FRENCH: Translation updates based on r34861
         34994: ITALIAN: Translation updates based on r34861
      35013: ALF-13561: Not found error after uploading new version
      - Fix by Pavel
      35034: Fixes ALF-13570: Error loading event info panel.
      35039: ALF-13573: Merged V3.4-BUG-FIX (3.4.9) to V4.0 (4.0.1)
         35022: ALF-13451: Allow modules to configure mimetypes
      35041: ALF-13466: Error is displayed by approve or reject wcm workflow
      - Fixed regression caused by ALF-4098
      - Protected calls to new addNewChildrenIfAny() method with isDirectory() checks
      35042: GERMAN: Translation updates based on r35029, and fixes ALF-12471.
      35043: SPANISH: Translation updates based on r35029, and fixes ALF-12471.
      35044: FRENCH: Translation updates based on r35029, and fixes ALF-12471.
      35045: ITALIAN: Translation updates based on r35029, and fixes ALF-12471.
      35046: JAPANESE: Translation updates based on r35029, and fixes ALF-12471.
      35047: DUTCH: Translation updates based on r35029, and fixes ALF-12471.
   35090: Remove Kofax.  It has been migrated to integrations/kofax
   35097: Added new file server cluster tests.
   Open for attributes only overlapped with open with oplock.
   Open with oplock with break to level II shared oplock.
   35099: JLAN Client updates to support level II oplocks, required by new cluster tests.
   35100: Various oplock related fixes, including problems opening file on second cluster node. ALF-13109.
   35107: remove errant '>'
   35116: ALF-13401 - Mac LION Powerpoint CIFS
   35162: Removed spurious attempt to force a concurrency exception for getNodePair after a node had actually been deleted.    Code would retry 50 times before failing.   Reviewed with Derek, its not the node service's job to second guess that there may be a concurrency problem in a client's cache.
   35164: Fix for ALF-13641 - Negative cases for date value in propertyNegative cases for date value in property. Today button
   35169: ALF-13401, ALF-12393: Added exception translation to AbstractReindexComponent retrying transactions, following change in r35162
   35172: ALF-13626: category.put.json.ftl has wrong bracket
   35173: ALF-12749 - CIFS: Editing of ppt/pptx files fails (MacOSx specific)
   35174: Fix for ALF-13556 - Sorting for custom model fields doesn't work for search results in Share
   35176: Fix for ALF-4281 - Script error at 'Email space users' form
   35186: Merged BRANCHES/DEV/DAM/V4.0-BUG-FIX-34847 to BRANCHES/DEV/V4.0-BUG-FIX:
      34875: Creating new branch from $FROM
      34939: Merged BRANCHES/DEV/DAM/V4.0-BUG-FIX-34397 to BRANCHES/DEV/DAM/V4.0-BUG-FIX-34847:
           34400: Creating new branch from $FROM
           34422: Merged DEV/DAM-0.1 to DEV/DAM/V4.0-BUG-FIX-34397
                 34085: Allow for generateThumbnailUrl to accept a rendition name parameter.
                 34086: Changed simpleView view type switch to integer implementation rather than boolean.
                 34087: Pulled specific rendering code for simple and detail view into separate view renderer objects.
                 34092: If simpleView was stored as a boolean convert it to an integer for ALF-12952.
           34423: Merged DEV/DAM/HEAD-34276 to DEV/DAM/V4.0-BUG-FIX-34397
                 34307: ALF-12952: Change DocumentList simpleView Nav Switch to an Int Implementation
      34957: ALF-12952: Change DocumentList simpleView Nav Switch to an Int Implementation
            - Removed ability to specify index on registerViewRenderer
            - Added firing of setupAdditionalViewRenderers to make it easier for extensions to register themselves at the appropriate time
      35021: ALF-12955: Share Document Library and Repository Browser Should Easily Allow for Additional Views
           - Changed viewRenderers to an object implementation with storage/retrieval via named properties or 'keys'
      35050: ALF-12955: Share Document Library and Repository Browser Should Easily Allow for Additional Views
           - Renamed simpleView preference and option to viewRendererName
           - Reintroduced simpleView boolean preference and option as deprecated to allow deletion of old preference
           - Renamed viewRendererOrder to viewRendererNames
           - Added default viewRendererNames at DocumentList.options level
           - Renamed widgets.simpleDetailed to widgets.viewRendererSelect but did NOT change HTML id for backwards compatibility
           - Renamed onSimpleDetailed to onViewRendererSelect
           - Added deletion of deprecated simpleView preference if it exists
      35056: ALF-12955: Share Document Library and Repository Browser Should Easily Allow for Additional Views
           - Made viewRenderer methods a proper Alfresco.ViewRenderer object which is more easily extended
           - Added name property to ViewRenderer constructor and changed registerViewRenderer to use that as a key
           - With more strictly defined ViewRenderers in place, changed select button to iterate over viewRendererNames rather than explicit list
      35104: ALF-12955: Share Document Library and Repository Browser Should Easily Allow for Additional Views
           - Added markup tag around the document list container
      35126: ALF-12955: Share Document Library and Repository Browser Should Easily Allow for Additional Views
           - Added markup tag documentListConstructorSetOptions around setOptions after DocumentList object constructor
           - Added markup tag documentListViewRendererSelect around view select buttons
           - Added markup tag documentListShowFolders around show folders button
           - Added markup tag documentListSortSelect around sort selection buttons
           - Renamed Alfresco.ViewRenderer to more specific Alfresco.DocumentListViewRenderer and private methods similarly
           - Added default for viewRendererName if it's undefined in options
           - Added check for availability of renderer specified in user preference, if not use default, and consolidated renderer index lookup
      35179: ALF-12955: Share Document Library and Repository Browser Should Easily Allow for Additional Views
           - Removed documentListConstructorSetOptions
   35194: Temp disable cifs text edit test.
   35197: ALF-13097 - IMAP templates have wrong mimetype
   35201: Merged V3.4-BUG-FIX to V4.0-BUG-FIX
      34462: Merged DEV to V3.4-BUG-FIX
         34461: ALF-10759: Advanced search fails for sub-element tags
            UITagSelector component which allows Advanced Search to add new tag option to search
      34479: Merged V3.4 to V3.4-BUG-FIX (RECORD ONLY)
         34477: ALF-13237: Yet another 13th hour Spring Surf Regression
           - Can't afford to pull in all the latest surf goodies so overriding PageImpl.class with one corresponding to Surf revision 1034 in WEB-INF/classes, just for 3.4.8
      34515: ALF-9855: Alfresco side to support standard Adobe-Japan1 PDF fonts in swftools
      - Bitrock binaries provided
      34518: ALF-13266: Ubuntu installation fails in non-obvious way when machine lacks sufficient memory
      - Fix from Bitrock
      - L10N required
      34536: Merged DEV to V3.4-BUG-FIX
         34529: ALF-13135: Impossible to Add new member on Workspace using email address
            NPE fix if AD users don't have e-mail address as a property.
      34538: ALF-12812	Saving files with apps on Mac OS X Lion in CIFS doesn't invoke rules (Update rule fires BEFORE, FileFolderInterceptor recalcs HIDDEN and TEMPORARY )
      34542: Add support for Level II shared oplock. ALF-13093, ALF-12328.
      Fixed CIFS open for attributes only access preventing oplock on the following file open.
      34543: Oplock and open for attributes fixes to the repo/AVM filesystems. ALF-13093, ALF-12328.
      34579: ALF-13284: Removing obselete files
      34603: ALF-10833 Alfresco does not show correct thumbnails for some specific kind of PDFs
         - Patched PDFRenderer-0.9.1 to return a null page if there was an error.
           The code structure did not lend itself to simply throwing the exception.
         - Modified PdfToImageContentTransformer to check for a null page and it then throws an AlfescoRuntimeException
           which causes the failover transformer to use the next transformer in the list: PDBBox which is able to
           transform the pdf and the image that was missing.
      34617: Add missing source Java folder.
      34629: ALF-13188: Content IO Channel not closed
      34697: ALF-13149: Start up performance suffers if the alf_transaction table grows too large.
      34712: ALF-13063: sample settings for DB2
      34803: New installer translations from Gloria
      34809: ALF-11956: Merged BELARUS/V3.4-BUG-FIX-2012_01_26 to V3.4-BUG-FIX (V3.4.9)
         << In addition to the 2 merged revisions, includes the change for ALF-11972 and test all-widgets.xsd >> 
         33715: ALF-11956: WCM accessibility   
         - sandbox name oriented titles were added almost to all action links at 'Browse Website' page view;
         - adding titles to image tags functionality was added to ActionLinkRenderer, UIMenu and UISandboxes (this includes arrow icons for 'Web Forms' and 'Modified Items');
         - titles were added to XForm Date/Time picker controls (text input and arrow buttons);
         - 'Click to edit' functionality via keyboard availability was added to XForms TinyMCE editor control (using 'Tab' key, 'Alt' + 'E' in IE or 'Alt' + 'Shift' + 'E' in FireFox);
         - additional i18n properties for Date/Time picker and action link titles were added
         34625: ALF-11956: WCM accessibility
         Increasing XForms widgets readability by screen reader tools:
         - Tiny MCE 3.2.7 buttons;
         - required fields;
         - inputs labels;
         - VGroup, HGroup and Repeating widgets folding icons/buttons and others
      ALF-11972: Title attributes for the WCM form element xs:anyURI not included to allow multiple xs:anyURI file picker "Select" buttons to be distinguished by screen readers
         - Change defined in JIRA
      34846: Translation updates:
         - FR: Missing Strings
         - DE: Fixes encoding issue
      34881: ALF-13512: Merged PATCHES/V3.4.8 to V3.4-BUG-FIX
         34829: ALF-12621: Sort order of folders including hyphens ( - ) are different in folder-tree and view on folders (in Share)
         - Switched from using JS sort to Java locale-based sort
         34845: ALF-12621: Fixed array typing problems in previous checkin
      34918: Fix for ALF-13385 Access DENIED api does not seem to work
      - changed default behaviour to any-deny-denies
      - config to switch back 
      - needs custom port to 4.0 for SOLR
      - unit tests added
      34919: Fix for ALF-13385 Access DENIED api does not seem to work
      - added property based configuration and default configuration check
      34937: ALF-11956: Merged BELARUS/V3.4-BUG-FIX-2012_01_26 to V3.4-BUG-FIX (V3.4.9)
         34886: ALF-11956: WCM accessibility
         - headings functionality is added. WAI-ARIA markup was used;
         - alert for XForms validation errors is added. WAI-ARIA markup was used;
         - previous accessibility changes tested and fixed against the new functionality
      35003: Merged HEAD to V3.4-BUG-FIX
         34673: Changed from time-based module and component names to GUID-based names.  Not likely to affect anything.
      35057: Fix for ALF-12590 Share - Document library doesn't return subfolders when parent space contains the character "- "
      - updated to the latest version of jaxen (which now includes saxpath)
      - the problem path is now parsed correctly
      35074: ALF-13597: Merged PATCHES/V3.4.6 to V3.4-BUG-FIX
         34978: ALF-13489: Index tracker now has ability to distinguish create/update/rename/link/unlink
         - Will prevent unnecessary cascading PATH regeneration on remote cluster nodes
         - QNames and noderefs of parents in index compared with those in the database
         - Experimental - needs testing
         34983: ALF-13489: Correction to renamed node detection
         34985: ALF-13489: Even more foolproof parent assoc cross-referencing
         - Should handle duplicate QNames, etc.
         - Renames now just an add and a remove
      35075: ALF-13598: Merged PATCHES/V3.4.6 to V3.4-BUG-FIX
         34872: Merged DEV (by Pavel) to PATCHES/V3.4.6 (and refactored)
            34554: ALF-11777 : Persistent lock is left on document in certain use cases when editing online (spp)
               1. From now documents are locked for maximum 24 hours when working through WebDAV/Vti.
               2. Session listeners were added for web-client and vti-module to allow handling session expiration event.
               3. WebDAVLockService class was implemented. It is used by session listeners to perform session cleaning (forcibly unlock all documents that were persistently locked during http session).
               4. LOCK/UNLOCK webdav methods and Get/Checkout/UncheckoutDocumentMethod vti methods where updated to correctly populate session list of locked documents.
            34832: ALF-11777 : Persistent lock is left on document in certain use cases when editing online (spp)
               1. From now documents are locked for maximum 24 hours when working through WebDAV/Vti.
               2. Session listener was added for webdav/vti to allow handling session expiration event.
               3. LOCK/UNLOCK webdav methods and Get/Checkout/UncheckoutDocumentMethod vti methods where updated to use shared code to lock/unlock nodes.
            34833: ALF-11777 : Persistent lock is left on document in certain use cases when editing online (spp)
               1. Remove unnecessary classes after 34554 rev.
            34852: ALF-11777 : Persistent lock is left on document in certain use cases when editing online (spp)
               1. Some changes after David's review of revisions 34832, 34833.
         34874: ALF-11777: Fixed typo
      35078: ALF-12785: BaseDownloadContentServlet could co into an infinite loop if asked to seek past the end of a file
      35079: ALF-12490 "HTTP Status 500 - 00200935 Exception in Transaction" message error with webform
         - ALF-9524 fix assumed there were only switch elements in a form
      35086: ALF-13563: Upgrade to Bitrock 8.1.0 to fix password validation issue
      35095: ALF-12764: New distributable alfresco-enterprise-ear-3.4.9.zip
      - Like war zip, but contains .ear file instead of .wars and also contains WAS shared library
      - Means samples and other bits are finally available to non-Tomcat users
      35103: Merged DEV to V3.4-BUG-FIX
         35098: ALF-12776: if a user requests to join a moderated site, and that request is rejected, the rejection email is sent to the user-id and not the email id.
         Implemented
            Correct WorkflowModelModeratedInvitation.WF_PROP_REVIEW_COMMENTS field in configuration for moderatedInvitationReviewTask
            Person's email into emailAction PARAM_TO
      35114: ALF-12766 Creating Web Content several users - different sandboxes
         - To be consistent with ALF-11440 PM comment 18-Dec-2011 and ALF-8787
           A Manager should only be able to create a file in a sandbox
           if it is NOT locked somewhere else.
         - Not much can be done about the error message as the locked path is useful in other
           situations and it is not possible to issue a different message on create only
      35121: ALF-11956: Merged BELARUS/V3.4-BUG-FIX-2012_04_05 to V3.4-BUG-FIX (V3.4.9)
         35109: ALF-11956: WCM accessibility
         - Date/Time Pickers are made accessible via the keyboard and readable by JAWS (13, demo version). WAI-ARIA standard is used;
         - corrected 'expanded' state determination for Date/Time Pickers;
         - Modified Items and Web Forms arrow buttons are made accessible via the keyboard on the Browse Website page;
         - some changes per the description of the issue and per the comment of the 23-Feb-12 11:33 AM
      35145: ALF-11990: CIFS login with case insensitive username is rejected
      - User name normalization moved to before MD4 hash retrieval
      35151: Port of oplock related changes from v4.x.
      35177: Fix for ALF-11936 - RSS feed from the activities dashlet produces invalid XML
      35178: ALF-12631: removeChild requires delete permissions on the child node, even when it is a secondary association
      - now it doesn't (thanks to Andy's solution)
      - new ACL_PRI_CHILD_ASSOC_ON_CHILD ACL entry only enforces the permission on the child node when it is a primary association
      35181: Merged DEV to V3.4-BUG-FIX
         35165: ALF-13409: Invite to a site throws an error if an instance of invitation-moderated-workflow is started by a user whose account is subsequently deleted
            InvitationServiceImpl listens for person node deletions (it already implements beforeDeleteNode) and cancels invitations within beforeDeleteNode
      35182: ALF-12567 Unable to create thumbnails for certain PDF files
         - The supplied PDF contains an invalid offset in the xref table. This turns out to be a quite common error resulting in
           thousands of Google hits. The offset is set to the string value "4294967295". This number in hex is FFFFFFFF. The value
           of an 4 byte int in C or Java with this value is -1. Neither PDFRenderer nor PDFBox have workarounds for this although
           lots of other systems do, which is why it is possible to view or edit it in other systems.
           Patched both PDFRenderer and PDFBox to handle this common error. 
      35185: ALF-13033: Friendlier error message when you try to delete non existent content from a sandbox
      35191: ALF-13409: Fix build.
      35192: Merged V3.4 to V3.4-BUG-FIX
         35161: ALF-13624: Merged V4.0-BUG-FIX to V3.4
            34474: ALF-13169 Tomcat fails to shut down
               - fix non daemon Timers (and punctuation!)
         35163: ALF-13656: Merged HEAD to V3.4
            31375: Fix for ALF-435 - Unfriendly error occurs when trying to delete renamed category from category page
         35189: Italian translations from Gloria
      35193: Merged V3.4 to V3.4-BUG-FIX (RECORD ONLY)
         35125: Merged V3.4-BUG-FIX to V3.4
         35156: Correction to merge in revision 35125 (a reintegrate merge rather than a selective merge)
   35202: Merged V3.4-BUG-FIX to V4.0-BUG-FIX (RECORD ONLY)
      34532: ALF-13233: Merged HEAD to V3.4-BUG-FIX
         32960: ALF-11008 - Support the WebDAV DELETE method in SPP/VTI, with the special response required by SPP for locked documents
      34559: ALF-13106: Merged HEAD to V3.4-BUG-FIX
         28223: Merged DEV/SWIFT to HEAD (Tika and Poi)
         30589: Upate Tika and add Ogg Vorbis support + tests
         30673: Upgrade POI and Tika for recent fixes
         31009: Bump the Tika version for some recent fixes
         31010: Update the test audio files to include more metadata
         31011: ALF-6170 Add missing audio model (needed in devcon demo)
         31013: Update the MP3 extractor to output audio keys (related to ALF-6170), and refactor the audio extractors to share more common code. Also expands the audio extractor tests to share common code, and test more metadata. (Needed for devcon demo)
         31022: Tika update for custom mimetypes enhancement
         31023: Add @since tags where known, and do a quick coding standards sweep
         31274: ALF-10813 follow-on - make it clearer that we're just creating the one detector, and switch to the new style version
         31289: ALF-10803 - Upgrade Tika to add the extra WordPerfect mimetype
         31553: ALF-10525 ACP mimetype detection fix, unit tests for it, and a NPE fix
         31554: Update Tika to get the fix for TIKA-764
         32105: ALF-11574 Upgrade Tika for the fix to TIKA-784, and add the DITA types to the Alfresco mimetype map
         32138: Bump the Tika version for the updated TIKA-784 fix, and add an Alfresco side unit test for this case
         32153: Update the vorbis jar to one that includes the license info more clearly in META-INF (without needing to read the POM)
         32320: ALF-11650 Upgrade Tika for TIKA-789 (MPP Detection), and add tests that show it is now being correctly handled
         32363: Update POI and Tika for the new code required to solve ALF-10980 (MPP Open/Change detection)
      34560: ALF-13106: Merged V4.0-BUG-FIX to V3.4-BUG-FIX
         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)
         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
         33779: Upgrade Tika for ALF-12714
         33782: ALF-12714 Add 3GPP/3GPP2 video, and MP4 Audio mimetypes
         33783: Update Tika for more MP4/QuickTime support, and enable MP4 audio metadata extraction + "quick" testing
      34561: ALF-13106: Fixed merge errors
      34562: ALF-13106: Merged SWIFT to V3.4-BUG-FIX
         26546: Have one copy of the Tika Config in spring, rather than several places fetching their own copy of the default one (either explicitly or implicitly).
      34563: ALF-13106: Merged HEAD to V3.4-BUG-FIX
         32264: Adding "quick" test resources for MS project.
      34564: ALF-13106: Fix unit test
      34752: GERMAN: Translation updates, based on EN: 34612
      34753: SPANISH: Translation updates, based on EN: 34612
      34755: FRENCH: Translation updates, based on EN: 34612
      34756: ITALIAN: Translation updates, based on EN: 34612
      34967: ALF-13552: Merged V4.0 to V3.4-BUG-FIX
         34932: ALF-13453: Enable XMLConstants.FEATURE_SECURE_PROCESSING feature on Transformer Factory to prevent remote code execution
            - Now SecureTransformerFactory should be used as a standard
      34971: ALF-13552: Merged V4.0 to V3.4-BUG-FIX
         34968: ALF-13453: Reversed XSLTProcessor and XSLTRenderingEngine changes for now as they break http://wiki.alfresco.com/wiki/WCM_Forms_Rendering and model handling via bsf extensions. A more sophisticated approach is required. See bug for more info.
      34982: ALF-13554: Merged V4.0 to V3.4-BUG-FIX
         34972: ALF-13340: Upgrade postgres JDBC driver to tested/supported version!
      34999: ALF-13552: Merged V4.0 to V3.4-BUG-FIX
         34997: ALF-13453, ALF-13565: Fully reverted revision 34932 as it prevents startup on Weblogic
      35000: Translation updates for DE, ES, IT. Based on EN r34846.
      35015: ALF-13451: Merged V4.0-BUG-FIX to V3.4-BUG-FIX
         33864: ALF-10736: JSF - Adding mimetype does not work on 3.4.x
      35020: ALF-13451: Merged V4.0-BUG-FIX to V3.4-BUG-FIX
         33863: ConfigSource for XMLConfigService which uses a ResourceFinder for wildcard-compatible lookups (UrlConfigSource does not support them)
      35029: JAPANESE: Translation updates based on EN r34846
   35212: ALF-13409: Deleting a person can now cancel their invitations. Cancelling invitations can delete inactive persons! So prevent infinite looping with a transaction local resource
   - Also fix up other invite related unit tests
   35217: Merged DEV to V4.0-BUG-FIX
      35214: ALF-12745 : AD-LDAP: alfresco hangs when upload user csv file
         Disable 'Upload User CSV File' button in Share admin console in case of AD-LDAP
   35221: Avoid a NPE if Repository.getPerson() is called when no RunAsUser is active, instead return Null as for users with no defined NodeRef


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@35229 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
2012-04-13 14:21:53 +00:00

1517 lines
58 KiB
Java

/*
* Copyright (C) 2005-2012 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>. */
package org.alfresco.repo.avm;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import org.alfresco.repo.avm.util.AVMUtil;
import org.alfresco.repo.domain.PropertyValue;
import org.alfresco.repo.domain.permissions.Acl;
import org.alfresco.repo.security.permissions.ACLCopyMode;
import org.alfresco.repo.security.permissions.ACLType;
import org.alfresco.service.cmr.avm.AVMBadArgumentException;
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.avmsync.AVMDifference;
import org.alfresco.service.cmr.avmsync.AVMSyncException;
import org.alfresco.service.cmr.avmsync.AVMSyncService;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.security.AccessPermission;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.NameMatcher;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* This implements APIs that allow comparison and synchronization
* of node trees as well as cumulative operations on layers to
* support various content production models.
* @author britt
*/
public class AVMSyncServiceImpl implements AVMSyncService
{
private static Log logger = LogFactory.getLog(AVMSyncServiceImpl.class);
/**
* The AVMService.
*/
private AVMService fAVMService;
/**
* The AVMRepository.
*/
private AVMRepository fAVMRepository;
/**
* The PermissionService
*/
private PermissionService fPermissionService;
/**
* Do nothing constructor.
*/
public AVMSyncServiceImpl()
{
}
/**
* Set the AVM Service. For Spring.
* @param avmService The AVMService reference.
*/
public void setAvmService(AVMService avmService)
{
fAVMService = avmService;
}
public void setAvmRepository(AVMRepository avmRepository)
{
fAVMRepository = avmRepository;
}
public void setPermissionService(PermissionService service)
{
fPermissionService = service;
}
/**
* Get a difference list between two corresponding node trees.
* @param srcVersion The version id for the source tree.
* @param srcPath The avm path to the source tree.
* @param dstVersion The version id for the destination tree.
* @param dstPath The avm path to the destination tree.
* @param excluder A NameMatcher used to exclude files from consideration.
* @return A List of AVMDifference structs which can be used for
* the update operation.
*/
public List<AVMDifference> compare(int srcVersion, String srcPath,
int dstVersion, String dstPath,
NameMatcher excluder)
{
return compare(srcVersion, srcPath, dstVersion, dstPath, excluder, false);
}
@Override
public List<AVMDifference> compare(int srcVersion, String srcPath, int dstVersion, String dstPath, NameMatcher excluder, boolean expandDirs)
{
long start = System.currentTimeMillis();
if (logger.isDebugEnabled())
{
logger.debug(srcPath + " : " + dstPath);
}
if (srcPath == null || dstPath == null)
{
throw new AVMBadArgumentException("Illegal null path.");
}
List<AVMDifference> result = new ArrayList<AVMDifference>();
AVMNodeDescriptor srcDesc = fAVMService.lookup(srcVersion, srcPath, true);
if (srcDesc == null)
{
throw new AVMSyncException("Source not found: " + srcPath);
}
AVMNodeDescriptor dstDesc = fAVMService.lookup(dstVersion, dstPath, true);
if (dstDesc == null)
{
// Special case: no pre-existing version in the destination.
result.add(new AVMDifference(srcVersion, srcPath, dstVersion, dstPath, AVMDifference.NEWER));
}
else
{
// Invoke the recursive implementation.
compare(srcVersion, srcDesc, dstVersion, dstDesc, result, excluder, true, expandDirs);
}
if (logger.isDebugEnabled())
{
logger.debug("Raw compare: [" + srcVersion + "," + srcPath + "][" + dstVersion + "," + dstPath + "][" + result.size() + "] in " + (System.currentTimeMillis() - start)
+ " msecs");
}
return result;
}
/**
* Internal recursive implementation of compare.
* @param srcVersion The version of the source tree.
* @param srcDesc The current source descriptor.
* @param dstVersion The version of the destination tree.
* @param dstDesc The current dstDesc
*/
private void compare(int srcVersion, AVMNodeDescriptor srcDesc,
int dstVersion, AVMNodeDescriptor dstDesc,
List<AVMDifference> result, NameMatcher excluder, boolean firstLevel, boolean expandDirs)
{
String srcPath = srcDesc.getPath();
String dstPath = dstDesc.getPath();
String srcParts[] = AVMUtil.splitBase(srcPath);
String srcChildName = srcParts[1];
String dstParts[] = AVMUtil.splitBase(dstPath);
String dstChildName = dstParts[1];
if ((dstChildName.equalsIgnoreCase(srcChildName)) && (! dstChildName.equals(srcChildName)))
{
// specific rename 'case'
String dstParentPath = dstParts[0];
if (dstParentPath == null)
{
dstParentPath = AVMUtil.buildAVMPath(AVMUtil.getStoreName(dstPath), "");
}
dstPath = AVMUtil.extendAVMPath(dstParentPath, srcChildName);
}
// Determine how the source and destination nodes differ.
if (excluder != null && (excluder.matches(srcPath) ||
excluder.matches(dstPath)))
{
return;
}
int diffCode = compareOne(srcDesc, dstDesc, false);
switch (diffCode)
{
case AVMDifference.SAME :
{
// A big short circuit.
return;
}
// The trivial to handle cases.
case AVMDifference.NEWER :
case AVMDifference.OLDER :
case AVMDifference.CONFLICT :
{
result.add(new AVMDifference(srcVersion, srcPath,
dstVersion, dstPath,
diffCode));
return;
}
case AVMDifference.DIRECTORY :
{
// First special case: source is a layered directory which points to
// the destinations path, and we are comparing 'head' versions.
if (srcDesc.isLayeredDirectory() &&
srcDesc.getIndirection().equalsIgnoreCase(dstPath) && srcVersion < 0 && dstVersion < 0)
{
// skip firstLevel (root)
if (! firstLevel)
{
// compare directory itself - eg. for an ACL change
int dirDiffCode = compareOne(srcDesc, dstDesc, true);
switch (dirDiffCode)
{
case AVMDifference.OLDER :
case AVMDifference.NEWER :
case AVMDifference.CONFLICT :
{
result.add(new AVMDifference(srcVersion, srcPath,
dstVersion, dstPath,
dirDiffCode));
// Also add all child items if necessary and any exists
if (expandDirs && srcDesc.isDirectory())
{
addNewChildrenIfAny(srcVersion, srcDesc, dstVersion, AVMNodeConverter.ExtendAVMPath(dstPath, dstDesc.getName()), result);
}
return; // short circuit
}
case AVMDifference.SAME :
{
break;
}
default :
{
throw new AVMSyncException("Invalid Difference Code " + dirDiffCode + " - Internal Error.");
}
}
}
// Get only a direct listing, since that's all that can be different.
Map<String, AVMNodeDescriptor> srcList =
fAVMService.getDirectoryListingDirect(srcDesc, true);
// The biggest shortcut: if the source directory is directly empty
// then we're done.
if (srcList.size() == 0)
{
return;
}
// We grab a complete listing of the destination.
Map<String, AVMNodeDescriptor> dstList =
fAVMService.getDirectoryListing(dstDesc, true);
for (String name : srcList.keySet())
{
AVMNodeDescriptor srcChild = srcList.get(name);
AVMNodeDescriptor dstChild = dstList.get(name);
String srcChildPath = srcChild.getPath();
String dstChildPath = AVMNodeConverter.ExtendAVMPath(dstPath, name);
if (excluder != null && (excluder.matches(srcChildPath) ||
excluder.matches(dstChildPath)))
{
continue;
}
if (dstChild == null)
{
// A missing destination child means the source is NEWER.
result.add(new AVMDifference(srcVersion, srcChildPath,
dstVersion, dstChildPath,
AVMDifference.NEWER));
// Also add all child items if necessary and any exists
if (expandDirs && srcChild.isDirectory())
{
addNewChildrenIfAny(srcVersion, srcChild, dstVersion, dstChildPath, result);
}
continue;
}
// Otherwise recursively invoke.
compare(srcVersion, srcChild,
dstVersion, dstChild,
result, excluder, false, expandDirs);
}
return;
}
// Second special case. Just as above but reversed.
if (dstDesc.isLayeredDirectory() &&
dstDesc.getIndirection().equalsIgnoreCase(srcPath) && srcVersion < 0 && dstVersion < 0)
{
// skip firstLevel (root)
if (! firstLevel)
{
// compare directory itself - eg. for an ACL change
int dirDiffCode = compareOne(srcDesc, dstDesc, true);
switch (dirDiffCode)
{
case AVMDifference.OLDER :
case AVMDifference.NEWER :
case AVMDifference.CONFLICT :
{
result.add(new AVMDifference(srcVersion, srcPath,
dstVersion, dstPath,
dirDiffCode));
return; // short circuit
}
case AVMDifference.SAME :
{
break;
}
default :
{
throw new AVMSyncException("Invalid Difference Code " + dirDiffCode + " - Internal Error.");
}
}
}
// Get direct content of destination.
Map<String, AVMNodeDescriptor> dstList =
fAVMService.getDirectoryListingDirect(dstDesc, true);
// Big short circuit.
if (dstList.size() == 0)
{
return;
}
// Get the source listing.
Map<String, AVMNodeDescriptor> srcList =
fAVMService.getDirectoryListing(srcDesc, true);
for (String name : dstList.keySet())
{
AVMNodeDescriptor dstChild = dstList.get(name);
AVMNodeDescriptor srcChild = srcList.get(name);
String srcChildPath = AVMNodeConverter.ExtendAVMPath(srcPath, name);
String dstChildPath = dstChild.getPath();
if (excluder != null && (excluder.matches(srcChildPath) ||
excluder.matches(dstChildPath)))
{
continue;
}
if (srcChild == null)
{
// Missing means the source is older.
result.add(new AVMDifference(srcVersion, srcChildPath,
dstVersion, dstChildPath,
AVMDifference.OLDER));
continue;
}
// Otherwise, recursively invoke.
compare(srcVersion, srcChild,
dstVersion, dstChild,
result, excluder, false, expandDirs);
}
return;
}
// Neither of the special cases apply, so brute force is the only answer.
Map<String, AVMNodeDescriptor> srcList =
fAVMService.getDirectoryListing(srcDesc, true);
Map<String, AVMNodeDescriptor> dstList =
fAVMService.getDirectoryListing(dstDesc, true);
// Iterate over the source.
for (String name : srcList.keySet())
{
AVMNodeDescriptor srcChild = srcList.get(name);
AVMNodeDescriptor dstChild = dstList.get(name);
String srcChildPath = srcChild.getPath();
String dstChildPath = AVMNodeConverter.ExtendAVMPath(dstPath, name);
if (excluder != null && (excluder.matches(srcChildPath) ||
excluder.matches(dstChildPath)))
{
continue;
}
if (dstChild == null)
{
// Not found in the destination means NEWER.
result.add(new AVMDifference(srcVersion, srcChildPath,
dstVersion, dstChildPath,
AVMDifference.NEWER));
continue;
}
// Otherwise recursive invocation.
compare(srcVersion, srcChild,
dstVersion, dstChild,
result, excluder, false, expandDirs);
}
// Iterate over the destination.
for (String name : dstList.keySet())
{
if (srcList.containsKey(name))
{
continue;
}
AVMNodeDescriptor dstChild = dstList.get(name);
String srcChildPath = AVMNodeConverter.ExtendAVMPath(srcPath, name);
String dstChildPath = dstChild.getPath();
if (excluder != null && (excluder.matches(srcChildPath) ||
excluder.matches(dstChildPath)))
{
continue;
}
// An entry not found in the source is OLDER.
result.add(new AVMDifference(srcVersion, srcChildPath,
dstVersion, dstChildPath,
AVMDifference.OLDER));
}
break;
}
default :
{
throw new AVMSyncException("Invalid Difference Code " + diffCode + " - Internal Error.");
}
}
}
private void addNewChildrenIfAny(int srcVersion, AVMNodeDescriptor srcChild, int dstVersion, String dstChildPath, List<AVMDifference> result)
{
Map<String, AVMNodeDescriptor> srcList = fAVMService.getDirectoryListingDirect(srcChild, true);
for (String name : srcList.keySet())
{
srcChild = srcList.get(name);
String srcChildPath = srcChild.getPath();
String dstPath = AVMNodeConverter.ExtendAVMPath(dstChildPath, name);
AVMNodeDescriptor dstDesc = fAVMService.lookup(dstVersion, dstChildPath, true);
int diffCode = AVMDifference.NEWER;
if (null == dstDesc)
{
diffCode = AVMDifference.NEWER;
}
result.add(new AVMDifference(srcVersion, srcChildPath, dstVersion, dstPath, diffCode));
if (srcChild.isDirectory())
{
addNewChildrenIfAny(srcVersion, srcChild, dstVersion, dstPath, result);
}
}
}
/**
* Updates the destination nodes in the AVMDifferences
* with the source nodes. Normally any conflicts or cases in
* which the source of an AVMDifference is older than the destination
* will cause the transaction to roll back.
* @param diffList A List of AVMDifference structs.
* @param excluder A possibly null name matcher to exclude unwanted updates.
* @param ignoreConflicts If this is true the update will skip those
* AVMDifferences which are in conflict with
* the destination.
* @param ignoreOlder If this is true the update will skip those
* AVMDifferences which have the source older than the destination.
* @param overrideConflicts If this is true the update will override conflicting
* AVMDifferences and replace the destination with the conflicting source.
* @param overrideOlder If this is true the update will override AVMDifferences
* @param tag Short update blurb.
* @param description Full update blurb.
* in which the source is older than the destination and overwrite the destination.
*/
public void update(List<AVMDifference> diffList,
NameMatcher excluder, boolean ignoreConflicts, boolean ignoreOlder,
boolean overrideConflicts, boolean overrideOlder, String tag, String description)
{
long start = System.currentTimeMillis();
Map<String, Integer> storeVersions = new HashMap<String, Integer>();
Set<String> destStores = new HashSet<String>();
Map<String, AVMDifference> diffsToUpdate = new TreeMap<String, AVMDifference>();
for (AVMDifference diff : diffList)
{
if (excluder != null && (excluder.matches(diff.getSourcePath()) ||
excluder.matches(diff.getDestinationPath())))
{
continue;
}
if (!diff.isValid())
{
throw new AVMSyncException("Malformed AVMDifference.");
}
diffsToUpdate.put(diff.getSourcePath(), diff);
}
for (AVMDifference diff : diffsToUpdate.values())
{
if (logger.isDebugEnabled())
{
logger.debug("update: " + diff);
}
// Snapshot the source if needed.
int version = diff.getSourceVersion();
if (version < 0)
{
String storeName = AVMUtil.getStoreName(diff.getSourcePath());
if (storeVersions.containsKey(storeName))
{
// We've already snapshotted this store.
version = storeVersions.get(storeName);
}
else
{
version = fAVMService.createSnapshot(storeName, "Snapshotted for submit.", null).get(storeName);
storeVersions.put(storeName, version);
}
}
AVMNodeDescriptor srcDesc = fAVMService.lookup(version,
diff.getSourcePath(), true);
String [] dstParts = AVMNodeConverter.SplitBase(diff.getDestinationPath());
if (dstParts[0] == null || diff.getDestinationVersion() >= 0)
{
// You can't have a root node as a destination.
throw new AVMSyncException("Invalid destination node: " + diff.getDestinationPath());
}
AVMNodeDescriptor dstDesc = fAVMService.lookup(-1, diff.getDestinationPath(), true);
// The default is that the source is newer in the case where
// the destination doesn't exist.
int diffCode = AVMDifference.NEWER;
if (dstDesc != null)
{
diffCode = compareOne(srcDesc, dstDesc, false);
}
// Keep track of stores updated so that they can all be snapshotted
// at end of update.
String dstPath = diff.getDestinationPath();
destStores.add(AVMUtil.getStoreName(dstPath));
dispatchUpdate(diffCode, dstParts[0], dstParts[1], excluder, srcDesc, dstDesc,
ignoreConflicts, ignoreOlder, overrideConflicts, overrideOlder);
}
for (String storeName : destStores)
{
fAVMService.createSnapshot(storeName, tag, description);
}
if (logger.isDebugEnabled())
{
logger.debug("Raw update: [" + diffList.size() + "] in " + (System.currentTimeMillis() - start) + " msecs");
}
}
private void dispatchUpdate(int diffCode, String parentPath, String name, NameMatcher excluder, AVMNodeDescriptor srcDesc, AVMNodeDescriptor dstDesc,
boolean ignoreConflicts, boolean ignoreOlder, boolean overrideConflicts, boolean overrideOlder)
{
// Dispatch.
switch (diffCode)
{
case AVMDifference.SAME :
{
// Nada to do.
return;
}
case AVMDifference.NEWER :
{
// You can't delete what isn't there.
linkIn(parentPath, name, srcDesc, excluder, dstDesc != null && !dstDesc.isDeleted(), dstDesc);
return;
}
case AVMDifference.OLDER :
{
// You can force it.
if (overrideOlder)
{
linkIn(parentPath, name, srcDesc, excluder, !dstDesc.isDeleted(), dstDesc);
return;
}
// You can ignore it.
if (ignoreOlder)
{
return;
}
// Or it's an error.
throw new AVMSyncException("Older version prevents update.");
}
case AVMDifference.CONFLICT :
{
// You can force it.
if (overrideConflicts)
{
linkIn(parentPath, name, srcDesc, excluder, true, dstDesc);
return;
}
// You can ignore it.
if (ignoreConflicts)
{
return;
}
// Or it's an error.
throw new AVMSyncException("Conflict prevents update.");
}
case AVMDifference.DIRECTORY :
{
int dirDiffCode = compareOne(srcDesc, dstDesc, true);
if (dirDiffCode == AVMDifference.DIRECTORY)
{
// error
throw new AVMSyncException("Unexpected diff code: " + dirDiffCode);
}
dispatchUpdate(dirDiffCode, parentPath, name, excluder, srcDesc, dstDesc,
ignoreConflicts, ignoreOlder, overrideConflicts, overrideOlder);
return;
}
default :
{
throw new AVMSyncException("Invalid Difference Code " + diffCode + " - Internal Error.");
}
}
}
/**
* Do the actual work of connecting nodes to the destination tree.
* @param parentPath The parent path the node will go in.
* @param name The name it will have.
* @param toLink The node descriptor.
* @param removeFirst Whether to do a removeNode before linking in.
*/
private void linkIn(String parentPath, String name, AVMNodeDescriptor toLink, NameMatcher excluder, boolean removeFirst, AVMNodeDescriptor dstDesc)
{
// This is a delete.
if (toLink == null)
{
try
{
fAVMService.removeNode(parentPath, name);
}
catch (AVMNotFoundException nfe)
{
// ignore
if (logger.isDebugEnabled())
{
logger.debug("linkIn: Does not exist: "+parentPath+"/"+name);
}
}
return;
}
mkdirs(parentPath, AVMNodeConverter.SplitBase(toLink.getPath())[0]);
if (toLink.isLayeredDirectory() && !toLink.isPrimary())
{
// Combining the remove and add into a single update API causes all sorts of potential security issues
if (removeFirst)
{
fAVMService.removeNode(parentPath, name);
}
recursiveCopy(parentPath, name, toLink, excluder);
return;
}
String newPath = AVMNodeConverter.ExtendAVMPath(parentPath, name);
if (toLink.isLayeredDirectory() &&
toLink.isPrimary() &&
dstDesc == null &&
toLink.getIndirection().equals(newPath))
{
recursiveCopy(parentPath, name, toLink, excluder);
return;
}
if (removeFirst)
{
if (toLink.isDirectory())
{
// Combining the remove and add into a single update API causes all sorts of potential security issues
fAVMService.removeNode(parentPath, name);
fAVMService.link(parentPath, name, toLink);
}
else
{
// this API only requires write access to the file
fAVMService.updateLink(parentPath, name, toLink);
}
}
else
{
fAVMService.link(parentPath, name, toLink);
}
setACL(parentPath, toLink.getPath(), newPath);
}
/*
* Get acl
*/
private Acl getACL(String path)
{
Lookup lookup = AVMRepository.GetInstance().lookup(-1, path, false);
if (lookup != null)
{
AVMNode node = lookup.getCurrentNode();
return node.getAcl();
}
else
{
return null;
}
}
/*
* Set ACL without COW
*/
private void setACL(String parentPath, String toCopyPath, String newPath)
{
Acl parentAcl= getACL(parentPath);
Acl acl = getACL(toCopyPath);
Lookup lookup = AVMRepository.GetInstance().lookup(-1, newPath, false);
if (lookup != null)
{
AVMNode newNode = lookup.getCurrentNode();
newNode.copyACLs(acl, parentAcl, ACLCopyMode.COPY);
AVMDAOs.Instance().fAVMNodeDAO.update(newNode);
}
else
{
return;
}
}
/**
* Recursively copy a node into the given position.
* @param parentPath The place to put it.
* @param name The name to give it.
* @param toCopy The it to put.
*/
private void recursiveCopy(String parentPath, String name, AVMNodeDescriptor toCopy, NameMatcher excluder)
{
fAVMService.createDirectory(parentPath, name);
String newParentPath = AVMNodeConverter.ExtendAVMPath(parentPath, name);
fAVMService.setMetaDataFrom(newParentPath, toCopy);
AVMNodeDescriptor parentDesc = fAVMService.lookup(-1, newParentPath, true);
Map<String, AVMNodeDescriptor> children =
fAVMService.getDirectoryListing(toCopy, true);
for (Map.Entry<String, AVMNodeDescriptor> entry : children.entrySet())
{
recursiveCopy(parentDesc, entry.getKey(), entry.getValue(), excluder);
}
}
/**
* Shortcutting helper that uses an AVMNodeDescriptor parent.
* @param parent The parent we are linking into.
* @param name The name to link in.
* @param toCopy The node to link in.
*/
private void recursiveCopy(AVMNodeDescriptor parent, String name, AVMNodeDescriptor toCopy, NameMatcher excluder)
{
String newPath = AVMNodeConverter.ExtendAVMPath(parent.getPath(), name);
if (excluder != null && (excluder.matches(newPath) ||
excluder.matches(toCopy.getPath())))
{
return;
}
// If it's a file or deleted simply link it in.
if (toCopy.isFile() || toCopy.isDeleted() || toCopy.isPlainDirectory())
{
fAVMRepository.link(parent, name, toCopy);
// needs to get the acl from the new location
setACL(parent.getPath(), toCopy.getPath(), newPath);
return;
}
// Otherwise make a directory in the target parent, and recursiveCopy all the source
// children into it.
AVMNodeDescriptor newParentDesc = fAVMRepository.createDirectory(parent, name);
fAVMService.setMetaDataFrom(newParentDesc.getPath(), toCopy);
Map<String, AVMNodeDescriptor> children =
fAVMService.getDirectoryListing(toCopy, true);
for (Map.Entry<String, AVMNodeDescriptor> entry : children.entrySet())
{
recursiveCopy(newParentDesc, entry.getKey(), entry.getValue(), excluder);
}
}
/**
* The workhorse of comparison and updating. Determine the versioning relationship
* of two nodes.
* @param srcDesc Descriptor for the source node.
* @param dstDesc Descriptor for the destination node.
* @return One of SAME, OLDER, NEWER, CONFLICT, DIRECTORY
*/
private int compareOne(AVMNodeDescriptor srcDesc, AVMNodeDescriptor dstDesc, boolean compareDir)
{
if (srcDesc == null)
{
return AVMDifference.OLDER;
}
if (srcDesc.getId() == dstDesc.getId())
{
// Identical
return AVMDifference.SAME;
}
// Check for mismatched fundamental types.
if ((srcDesc.isDirectory() && dstDesc.isFile()) ||
(srcDesc.isFile() && dstDesc.isDirectory()))
{
if (logger.isInfoEnabled())
{
logger.info("compareOne(1): conflict ["+srcDesc+","+dstDesc+"]");
}
return AVMDifference.CONFLICT;
}
// A deleted node on either side means uniform handling because
// a deleted node can be the descendent of any other type of node.
if (srcDesc.isDeleted() || dstDesc.isDeleted())
{
AVMNodeDescriptor common = fAVMService.getCommonAncestor(srcDesc, dstDesc);
if (common == null)
{
if (logger.isInfoEnabled())
{
logger.info("compareOne(2): conflict ["+srcDesc+","+dstDesc+"]");
}
return AVMDifference.CONFLICT;
}
if (common.getId() == srcDesc.getId())
{
return AVMDifference.OLDER;
}
if (common.getId() == dstDesc.getId())
{
return AVMDifference.NEWER;
}
if (common.isLayeredFile())
{
Integer diff = compareLayeredCommonAncestor(common, srcDesc, dstDesc);
if (diff != null)
{
return diff;
}
}
if (srcDesc.isDeleted() &&
((srcDesc.getDeletedType() == AVMNodeType.LAYERED_DIRECTORY) || (srcDesc.getDeletedType() == AVMNodeType.LAYERED_FILE)))
{
Integer diff = compareLayeredCommonAncestor(common, srcDesc, dstDesc);
if (diff != null)
{
return diff;
}
}
else if (dstDesc.isDeleted() &&
((dstDesc.getDeletedType() == AVMNodeType.LAYERED_DIRECTORY) || (dstDesc.getDeletedType() == AVMNodeType.LAYERED_FILE)))
{
Integer diff = compareLayeredCommonAncestor(common, dstDesc, srcDesc);
if (diff != null)
{
return diff;
}
}
// Must be a conflict.
if (logger.isInfoEnabled())
{
logger.info("compareOne(3): conflict ["+srcDesc+","+dstDesc+"]");
}
return AVMDifference.CONFLICT;
}
if (srcDesc.isDirectory() && dstDesc.isDirectory())
{
// Both source and destination are both some kind of directory.
if (! compareDir)
{
// note: the DIRECTORY difference code never gets returned to external callers of compare.
return AVMDifference.DIRECTORY;
}
else
{
// Matched directories that are not identical should be compared (initially) based on ACLs to see if they're newer, older or in conflict
if ((srcDesc.isLayeredDirectory() && srcDesc.getIndirection().equals(dstDesc.getPath())) ||
(dstDesc.isLayeredDirectory() && dstDesc.getIndirection().equals(srcDesc.getPath())))
{
// Either: Source is a layered directory and points at the destination plain/layered directory
// Or: Destination is a layered directory and points at the source plain directory
// Check properties (eg. title/description)
if (compareNodeProps(srcDesc, dstDesc) == AVMDifference.SAME)
{
// Check ACLs
int dirDiffCode = compareACLs(srcDesc, dstDesc);
if (dirDiffCode != AVMDifference.CONFLICT)
{
return dirDiffCode;
}
if (logger.isInfoEnabled())
{
logger.info("compareOne(4): conflict ["+srcDesc+","+dstDesc+"]");
}
}
// drop through to check common ancestor
}
// Check common ancestor
AVMNodeDescriptor common = fAVMService.getCommonAncestor(srcDesc, dstDesc);
// Conflict case.
if (common == null)
{
if (logger.isInfoEnabled())
{
logger.info("compareOne(5): conflict ["+srcDesc+","+dstDesc+"]");
}
return AVMDifference.CONFLICT;
}
if (common.getId() == srcDesc.getId())
{
return AVMDifference.OLDER;
}
if (common.getId() == dstDesc.getId())
{
return AVMDifference.NEWER;
}
if (logger.isInfoEnabled())
{
logger.info("compareOne(6): conflict ["+srcDesc+","+dstDesc+"]");
}
// They must, finally, be in conflict.
return AVMDifference.CONFLICT;
}
}
// At this point both source and destination are both some kind of file.
if (srcDesc.isLayeredFile())
{
// Handle the layered file source case.
if (dstDesc.isPlainFile())
{
// We consider a layered source file that points at the destination
// file SAME.
if (srcDesc.getIndirection().equals(dstDesc.getPath()))
{
return AVMDifference.SAME;
}
if (logger.isInfoEnabled())
{
logger.info("compareOne(7): conflict ["+srcDesc+","+dstDesc+"]");
}
// We know that they are in conflict since they are of different types.
return AVMDifference.CONFLICT;
}
// Destination is a layered file also.
AVMNodeDescriptor common = fAVMService.getCommonAncestor(srcDesc, dstDesc);
if (common == null)
{
if (logger.isInfoEnabled())
{
logger.info("compareOne(8): conflict ["+srcDesc+","+dstDesc+"]");
}
return AVMDifference.CONFLICT;
}
if (common.getId() == srcDesc.getId())
{
return AVMDifference.OLDER;
}
if (common.getId() == dstDesc.getId())
{
return AVMDifference.NEWER;
}
if (logger.isInfoEnabled())
{
logger.info("compareOne(9): conflict ["+srcDesc+","+dstDesc+"]");
}
// Finally we know they are in conflict.
return AVMDifference.CONFLICT;
}
// Source is a plain file.
if (dstDesc.isLayeredFile())
{
// We consider a source file that is the target of a layered destination file to be
// SAME.
if (dstDesc.getIndirection().equals(srcDesc.getPath()))
{
return AVMDifference.SAME;
}
AVMNodeDescriptor common = fAVMService.getCommonAncestor(srcDesc, dstDesc);
if (common == null)
{
if (logger.isInfoEnabled())
{
logger.info("compareOne(10): conflict ["+srcDesc+","+dstDesc+"]");
}
return AVMDifference.CONFLICT;
}
if (common.getId() == srcDesc.getId())
{
return AVMDifference.OLDER;
}
if (common.getId() == dstDesc.getId())
{
return AVMDifference.NEWER;
}
if (common.isLayeredFile())
{
Integer diff = compareLayeredCommonAncestor(common, srcDesc, dstDesc);
if (diff != null)
{
return diff;
}
}
if (logger.isInfoEnabled())
{
logger.info("compareOne(11): conflict ["+srcDesc+","+dstDesc+"]");
}
return AVMDifference.CONFLICT;
}
// Destination is a plain file.
AVMNodeDescriptor common = fAVMService.getCommonAncestor(srcDesc, dstDesc);
// Conflict case.
if (common == null)
{
if (logger.isInfoEnabled())
{
logger.info("compareOne(12): conflict ["+srcDesc+","+dstDesc+"]");
}
return AVMDifference.CONFLICT;
}
if (common.getId() == srcDesc.getId())
{
return AVMDifference.OLDER;
}
if (common.getId() == dstDesc.getId())
{
return AVMDifference.NEWER;
}
if (common.isLayeredFile())
{
Integer diff = compareLayeredCommonAncestor(common, srcDesc, dstDesc);
if (diff != null)
{
return diff;
}
}
if (logger.isInfoEnabled())
{
logger.info("compareOne(13): conflict ["+srcDesc+","+dstDesc+"]");
}
// They must, finally, be in conflict.
return AVMDifference.CONFLICT;
}
private Integer compareLayeredCommonAncestor(AVMNodeDescriptor common, AVMNodeDescriptor srcDesc, AVMNodeDescriptor dstDesc)
{
Integer diff = null;
// check dst ancestry
diff = compareLayeredCommonAncestor(common, dstDesc.getId(), AVMDifference.NEWER);
if (diff == null)
{
// check src ancestry
diff = compareLayeredCommonAncestor(common, srcDesc.getId(), AVMDifference.OLDER);
}
return diff;
}
private Integer compareLayeredCommonAncestor(AVMNodeDescriptor common, long compareNodeId, int diffType)
{
Integer diff = null;
AVMNode compareAncNode = AVMDAOs.Instance().fAVMNodeDAO.getByID(compareNodeId).getAncestor();
if (compareAncNode != null)
{
if (common.getId() == compareAncNode.getId())
{
diff = diffType;
}
else if (common.isLayeredFile() || (compareAncNode.getType() == AVMNodeType.LAYERED_FILE))
{
// TODO review (alongside createSnapshot+COW)
diff = compareLayeredCommonAncestor(common, compareAncNode.getId(), diffType);
}
}
return diff;
}
// compare node properties
private int compareNodeProps(AVMNodeDescriptor srcDesc, AVMNodeDescriptor dstDesc)
{
Map<QName, PropertyValue> srcProps = fAVMService.getNodeProperties(srcDesc);
Map<QName, PropertyValue> dstProps = fAVMService.getNodeProperties(dstDesc);
if (srcProps.size() == dstProps.size())
{
for (Map.Entry<QName, PropertyValue> srcEntry : srcProps.entrySet())
{
PropertyValue srcValue = srcEntry.getValue();
PropertyValue dstValue = dstProps.get(srcEntry.getKey());
if ((srcValue == null) && (dstValue == null))
{
continue;
}
else if ((srcValue != null) && (dstValue != null) &&
(srcValue.equals(dstValue)))
{
continue;
}
else
{
if (logger.isInfoEnabled())
{
logger.info("compareNodeProps(1): conflict ["+srcDesc+","+dstDesc+"]");
}
return AVMDifference.CONFLICT;
}
}
return AVMDifference.SAME;
}
if (logger.isInfoEnabled())
{
logger.info("compareNodeProps(2): conflict ["+srcDesc+","+dstDesc+"]");
}
return AVMDifference.CONFLICT;
}
// compare ACLs
private int compareACLs(AVMNodeDescriptor srcDesc, AVMNodeDescriptor dstDesc)
{
Acl srcAcl = getACL(srcDesc.getPath());
Acl dstAcl = getACL(dstDesc.getPath());
if ((srcAcl == null) && (dstAcl == null))
{
return AVMDifference.SAME;
}
else if (srcAcl != null)
{
if ((dstAcl != null) && (srcAcl.getAclId() == dstAcl.getAclId()))
{
return AVMDifference.SAME;
}
if (srcAcl.getAclType().equals(ACLType.LAYERED))
{
if ((dstAcl == null) || dstAcl.getAclType().equals(ACLType.SHARED) || dstAcl.getAclType().equals(ACLType.LAYERED) || dstAcl.getAclType().equals(ACLType.DEFINING))
{
return AVMDifference.SAME;
}
else
{
// TODO review
throw new AVMSyncException("srcAcl type: " + srcAcl.getAclType() + ", unexpected dstAcl type: " + dstAcl.getAclType());
}
}
else if (srcAcl.getAclType().equals(ACLType.DEFINING))
{
if ((dstAcl == null) || dstAcl.getAclType().equals(ACLType.SHARED) || dstAcl.getAclType().equals(ACLType.LAYERED))
{
return AVMDifference.NEWER;
}
else if (dstAcl.getAclType().equals(ACLType.DEFINING))
{
boolean same = compareACEs(srcDesc, dstDesc);
if (same)
{
return AVMDifference.SAME;
}
}
else
{
// TODO review
throw new AVMSyncException("srcAcl type: " + srcAcl.getAclType() + ", unexpected dstAcl type: " + dstAcl.getAclType());
}
}
else if (srcAcl.getAclType().equals(ACLType.SHARED))
{
if ((dstAcl == null) || dstAcl.getAclType().equals(ACLType.SHARED))
{
boolean same = compareACEs(srcDesc, dstDesc);
if (same)
{
return AVMDifference.SAME;
}
}
else
{
// TODO review
throw new AVMSyncException("srcAcl type: " + srcAcl.getAclType() + ", unexpected dstAcl type: " + dstAcl.getAclType());
}
}
}
else if (srcAcl == null)
{
if (dstAcl != null)
{
return AVMDifference.SAME;
}
}
if (logger.isInfoEnabled())
{
logger.info("compareACLs: conflict ["+srcDesc+","+dstDesc+"]");
}
return AVMDifference.CONFLICT;
}
private boolean compareACEs(AVMNodeDescriptor srcDesc, AVMNodeDescriptor dstDesc)
{
boolean same = false;
NodeRef srcNodeRef = AVMNodeConverter.ToNodeRef(-1, srcDesc.getPath());
Set<AccessPermission> srcSet = fPermissionService.getAllSetPermissions(srcNodeRef);
NodeRef dstNodeRef = AVMNodeConverter.ToNodeRef(-1, dstDesc.getPath());
Set<AccessPermission> dstSet = fPermissionService.getAllSetPermissions(dstNodeRef);
if (srcSet.size() == dstSet.size())
{
same = true;
for (AccessPermission srcPerm : srcSet)
{
boolean found = false;
for (AccessPermission dstPerm : dstSet)
{
if (compareAccessPermission(srcPerm, dstPerm))
{
found = true;
break;
}
}
if (! found)
{
same = false;
break;
}
}
}
return same;
}
private boolean compareAccessPermission(AccessPermission srcPerm, AccessPermission dstPerm)
{
// TODO: currently ignores position (refer to updated AccessPermissionImpl.equals)
if (srcPerm == dstPerm)
{
return true;
}
if (srcPerm == null)
{
return false;
}
if (srcPerm.getAccessStatus() == null)
{
if (dstPerm.getAccessStatus() != null)
{
return false;
}
}
else if (! srcPerm.getAccessStatus().equals(dstPerm.getAccessStatus()))
{
return false;
}
if (srcPerm.getAuthority() == null)
{
if (dstPerm.getAuthority() != null)
{
return false;
}
}
else if (! srcPerm.getAuthority().equals(dstPerm.getAuthority()))
{
return false;
}
if (srcPerm.getPermission() == null)
{
if (dstPerm.getPermission() != null)
{
return false;
}
}
else if (! srcPerm.getPermission().equals(dstPerm.getPermission()))
{
return false;
}
return true;
}
/**
* Flattens a layer so that all all nodes under and including
* <code>layerPath</code> become translucent to any nodes in the
* corresponding location under and including <code>underlyingPath</code>
* that are the same version.
* @param layerPath The overlying layer path.
* @param underlyingPath The underlying path.
*/
public void flatten(String layerPath, String underlyingPath)
{
long start = System.currentTimeMillis();
if (layerPath == null || underlyingPath == null)
{
throw new AVMBadArgumentException("Illegal null path.");
}
AVMNodeDescriptor layerNode = fAVMService.lookup(-1, layerPath, true);
if (layerNode == null)
{
throw new AVMNotFoundException("Not found: " + layerPath);
}
AVMNodeDescriptor underlyingNode = fAVMService.lookup(-1, underlyingPath, true);
if (underlyingNode == null)
{
throw new AVMNotFoundException("Not found: " + underlyingPath);
}
flatten(layerNode, underlyingNode);
if (logger.isDebugEnabled())
{
logger.debug("Raw flatten: " + layerNode + " " + underlyingNode + " in " + (System.currentTimeMillis() - start) + " msecs");
}
}
/**
* This is the implementation of flatten.
* @param layer The on top node.
* @param underlying The underlying node.
*/
private boolean flatten(AVMNodeDescriptor layer, AVMNodeDescriptor underlying)
{
if (logger.isDebugEnabled())
{
logger.debug("flatten: " + layer + " " + underlying);
}
if (!layer.isLayeredDirectory())
{
return false;
}
// layer and underlying must match for flattening to be useful.
if (!layer.getIndirection().equalsIgnoreCase(underlying.getPath()))
{
return false;
}
// The underlying thing must be a directory.
if (!underlying.isDirectory())
{
return false;
}
Map<String, AVMNodeDescriptor> layerListing =
fAVMService.getDirectoryListingDirect(-1, layer.getPath(), true);
// If the layer is empty (directly, that is) we're done.
if (layerListing.size() == 0)
{
return true;
}
// Grab the listing
Map<String, AVMNodeDescriptor> underListing =
fAVMService.getDirectoryListing(underlying, true);
boolean flattened = true;
for (String name : layerListing.keySet())
{
AVMNodeDescriptor topNode = layerListing.get(name);
AVMNodeDescriptor bottomNode = underListing.get(name);
if (logger.isTraceEnabled())
{
logger.trace("Trying to flatten out: " + name);
}
if (bottomNode == null)
{
if (logger.isTraceEnabled())
{
logger.trace("Can't flatten (no bottomNode): " + name);
}
flattened = false;
continue;
}
// We've found an identity so flatten it.
if (topNode.getId() == bottomNode.getId())
{
fAVMRepository.flatten(layer.getPath(), name);
if (logger.isTraceEnabled())
{
logger.trace("Identity flattened: " + name);
}
}
else
{
if (bottomNode.isLayeredDirectory())
{
AVMNodeDescriptor lookup = fAVMService.lookup(bottomNode.getIndirectionVersion(), bottomNode.getIndirection());
if (lookup == null)
{
if (logger.isDebugEnabled())
{
logger.debug("Can't flatten (no bottomNode indirection): " + name);
}
flattened = false;
continue;
}
}
// Otherwise recursively flatten the children.
if (flatten(topNode, bottomNode))
{
fAVMRepository.flatten(layer.getPath(), name);
if (logger.isTraceEnabled())
{
logger.trace("Recursively flattened: " + name);
}
}
else
{
flattened = false;
}
}
}
return flattened;
}
/**
* Takes a layer, deletes it and recreates it pointing at the same underlying
* node. Any changes in the layer are lost (except to history if the layer has been
* snapshotted.)
*
* NB: fixed to respect permissions and allow reset end preview sandboxes by finding all direct children and flattening
*
* @param layerPath
*/
public void resetLayer(String layerPath)
{
long start = System.currentTimeMillis();
AVMNodeDescriptor desc = fAVMService.lookup(-1, layerPath);
if (desc == null)
{
throw new AVMNotFoundException("Not Found: " + layerPath);
}
Map<String, AVMNodeDescriptor> layerListing =
fAVMService.getDirectoryListingDirect(-1, layerPath, true);
for (String name : layerListing.keySet())
{
fAVMRepository.flatten(layerPath, name);
}
if (logger.isDebugEnabled())
{
logger.debug("Raw resetLayer: " + layerPath + " in " + (System.currentTimeMillis() - start) + " msecs");
}
}
/**
* Make sure this entire directory path exists.
* @param path
* @param sourcePath
*/
private void mkdirs(String path, String sourcePath)
{
if (fAVMService.lookup(-1, path) != null)
{
return;
}
String [] pathParts = AVMNodeConverter.SplitBase(path);
if (pathParts[0] == null)
{
// This is a root path and as such has to exist.
// Something else is going on.
throw new AVMSyncException("No corresponding destination path: " + path);
}
mkdirs(pathParts[0], AVMNodeConverter.SplitBase(sourcePath)[0]);
fAVMService.createDirectory(pathParts[0], pathParts[1]);
fAVMService.setMetaDataFrom(path, fAVMService.lookup(-1, sourcePath));
}
}