mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-06-30 18:15:39 +00:00
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
4027 lines
158 KiB
Java
4027 lines
158 KiB
Java
/*
|
|
* Copyright (C) 2005-2010 Alfresco Software Limited.
|
|
*
|
|
* This file is part of Alfresco
|
|
*
|
|
* Alfresco is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Alfresco is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
package org.alfresco.filesys.repo;
|
|
|
|
import java.io.FileNotFoundException;
|
|
import java.io.IOException;
|
|
import java.io.Serializable;
|
|
import java.net.InetAddress;
|
|
import java.util.Arrays;
|
|
import java.util.Date;
|
|
import java.util.HashMap;
|
|
import java.util.LinkedList;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
import java.util.TreeSet;
|
|
import java.util.concurrent.Callable;
|
|
|
|
import javax.transaction.UserTransaction;
|
|
|
|
import org.alfresco.error.AlfrescoRuntimeException;
|
|
import org.alfresco.filesys.alfresco.AlfrescoContext;
|
|
import org.alfresco.filesys.alfresco.AlfrescoDiskDriver;
|
|
import org.alfresco.filesys.alfresco.AlfrescoNetworkFile;
|
|
import org.alfresco.jlan.server.SrvSession;
|
|
import org.alfresco.jlan.server.core.DeviceContext;
|
|
import org.alfresco.jlan.server.core.DeviceContextException;
|
|
import org.alfresco.jlan.server.filesys.AccessDeniedException;
|
|
import org.alfresco.jlan.server.filesys.AccessMode;
|
|
import org.alfresco.jlan.server.filesys.DirectoryNotEmptyException;
|
|
import org.alfresco.jlan.server.filesys.DiskDeviceContext;
|
|
import org.alfresco.jlan.server.filesys.DiskFullException;
|
|
import org.alfresco.jlan.server.filesys.DiskInterface;
|
|
import org.alfresco.jlan.server.filesys.DiskSizeInterface;
|
|
import org.alfresco.jlan.server.filesys.FileAttribute;
|
|
import org.alfresco.jlan.server.filesys.FileInfo;
|
|
import org.alfresco.jlan.server.filesys.FileName;
|
|
import org.alfresco.jlan.server.filesys.FileOpenParams;
|
|
import org.alfresco.jlan.server.filesys.FileSharingException;
|
|
import org.alfresco.jlan.server.filesys.FileStatus;
|
|
import org.alfresco.jlan.server.filesys.NetworkFile;
|
|
import org.alfresco.jlan.server.filesys.SearchContext;
|
|
import org.alfresco.jlan.server.filesys.SrvDiskInfo;
|
|
import org.alfresco.jlan.server.filesys.TreeConnection;
|
|
import org.alfresco.jlan.server.filesys.cache.FileState;
|
|
import org.alfresco.jlan.server.filesys.cache.FileStateLockManager;
|
|
import org.alfresco.jlan.server.filesys.pseudo.MemoryNetworkFile;
|
|
import org.alfresco.jlan.server.filesys.pseudo.PseudoFile;
|
|
import org.alfresco.jlan.server.filesys.pseudo.PseudoFileInterface;
|
|
import org.alfresco.jlan.server.filesys.pseudo.PseudoFileList;
|
|
import org.alfresco.jlan.server.filesys.pseudo.PseudoNetworkFile;
|
|
import org.alfresco.jlan.server.filesys.quota.QuotaManager;
|
|
import org.alfresco.jlan.server.filesys.quota.QuotaManagerException;
|
|
import org.alfresco.jlan.server.locking.FileLockingInterface;
|
|
import org.alfresco.jlan.server.locking.LockManager;
|
|
import org.alfresco.jlan.server.locking.OpLockInterface;
|
|
import org.alfresco.jlan.server.locking.OpLockManager;
|
|
import org.alfresco.jlan.smb.SharingMode;
|
|
import org.alfresco.jlan.smb.WinNT;
|
|
import org.alfresco.jlan.smb.server.SMBServer;
|
|
import org.alfresco.jlan.smb.server.SMBSrvSession;
|
|
import org.alfresco.jlan.util.MemorySize;
|
|
import org.alfresco.jlan.util.WildCard;
|
|
import org.alfresco.model.ContentModel;
|
|
import org.alfresco.repo.admin.SysAdminParams;
|
|
import org.alfresco.repo.node.archive.NodeArchiveService;
|
|
import org.alfresco.repo.policy.BehaviourFilter;
|
|
import org.alfresco.repo.security.authentication.AuthenticationContext;
|
|
import org.alfresco.repo.security.authentication.AuthenticationUtil;
|
|
import org.alfresco.repo.transaction.RetryingTransactionHelper;
|
|
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
|
import org.alfresco.service.cmr.lock.LockService;
|
|
import org.alfresco.service.cmr.lock.LockType;
|
|
import org.alfresco.service.cmr.lock.NodeLockedException;
|
|
import org.alfresco.service.cmr.model.FileFolderService;
|
|
import org.alfresco.service.cmr.repository.ContentData;
|
|
import org.alfresco.service.cmr.repository.ContentIOException;
|
|
import org.alfresco.service.cmr.repository.ContentService;
|
|
import org.alfresco.service.cmr.repository.MimetypeService;
|
|
import org.alfresco.service.cmr.repository.NodeRef;
|
|
import org.alfresco.service.cmr.repository.NodeService;
|
|
import org.alfresco.service.cmr.repository.StoreRef;
|
|
import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
|
|
import org.alfresco.service.cmr.search.SearchService;
|
|
import org.alfresco.service.cmr.security.AccessPermission;
|
|
import org.alfresco.service.cmr.security.AccessStatus;
|
|
import org.alfresco.service.cmr.security.AuthenticationService;
|
|
import org.alfresco.service.cmr.security.PermissionService;
|
|
import org.alfresco.service.namespace.NamespaceService;
|
|
import org.alfresco.service.namespace.QName;
|
|
import org.alfresco.util.Pair;
|
|
import org.apache.commons.logging.Log;
|
|
import org.apache.commons.logging.LogFactory;
|
|
import org.springframework.extensions.config.ConfigElement;
|
|
|
|
/**
|
|
* Content repository filesystem driver class
|
|
*
|
|
* <p>Provides a filesystem interface for various protocols such as SMB/CIFS and FTP.
|
|
*
|
|
* @author gkspencer
|
|
*/
|
|
public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterface, FileLockingInterface, OpLockInterface, DiskSizeInterface
|
|
{
|
|
// Logging
|
|
|
|
private static final Log logger = LogFactory.getLog(ContentDiskDriver.class);
|
|
|
|
// Configuration key names
|
|
|
|
private static final String KEY_STORE = "store";
|
|
private static final String KEY_ROOT_PATH = "rootPath";
|
|
private static final String KEY_RELATIVE_PATH = "relativePath";
|
|
|
|
// File status values used in the file state cache
|
|
|
|
public static final int FileUnknown = FileStatus.Unknown;
|
|
public static final int FileNotExist = FileStatus.NotExist;
|
|
public static final int FileExists = FileStatus.FileExists;
|
|
public static final int DirectoryExists = FileStatus.DirectoryExists;
|
|
|
|
public static final int CustomFileStatus= FileStatus.MaxStatus + 1;
|
|
public static final int FileRenamed = CustomFileStatus;
|
|
public static final int DeleteOnClose = CustomFileStatus + 1;
|
|
|
|
// File state attributes
|
|
|
|
public static final String AttrLinkNode = "ContentLinkNode";
|
|
|
|
// List of content properties to copy during rename
|
|
|
|
private static QName[] _copyProperties = { ContentModel.PROP_AUTHOR, ContentModel.PROP_TITLE, ContentModel.PROP_DESCRIPTION };
|
|
|
|
// List of property namespaces to exclude from copy during rename
|
|
|
|
private static Set<String> _excludedNamespaces = new TreeSet<String>(Arrays.asList(new String[]
|
|
{
|
|
NamespaceService.CONTENT_MODEL_1_0_URI, NamespaceService.SYSTEM_MODEL_1_0_URI
|
|
}));
|
|
|
|
// Disk sizing contants
|
|
|
|
private static final int DiskBlockSize = 512; // bytes per block
|
|
private static final long DiskAllocationUnit = 32 * MemorySize.KILOBYTE;
|
|
private static final long DiskBlocksPerUnit = DiskAllocationUnit / DiskBlockSize;
|
|
|
|
// Disk size returned in the content store does not support free/total size
|
|
|
|
protected static final long DiskSizeDefault = 1 * MemorySize.TERABYTE;
|
|
protected static final long DiskFreeDefault = DiskSizeDefault / 2;
|
|
|
|
// Services and helpers
|
|
|
|
private CifsHelper cifsHelper;
|
|
private NamespaceService namespaceService;
|
|
private NodeService nodeService;
|
|
private SearchService searchService;
|
|
private ContentService contentService;
|
|
private MimetypeService mimetypeService;
|
|
private PermissionService permissionService;
|
|
private FileFolderService fileFolderService;
|
|
private NodeArchiveService nodeArchiveService;
|
|
private LockService lockService;
|
|
private DictionaryService dictionaryService;
|
|
|
|
private AuthenticationContext authContext;
|
|
private AuthenticationService authService;
|
|
private SysAdminParams sysAdminParams;
|
|
|
|
private BehaviourFilter policyBehaviourFilter;
|
|
|
|
// Node monitor factory
|
|
|
|
private NodeMonitorFactory m_nodeMonitorFactory;
|
|
|
|
// Lock manager
|
|
|
|
private static FileStateLockManager _lockManager;
|
|
|
|
/**
|
|
* Class constructor
|
|
*
|
|
* @param serviceRegistry to connect to the repository services
|
|
*/
|
|
public ContentDiskDriver(CifsHelper cifsHelper)
|
|
{
|
|
this.cifsHelper = cifsHelper;
|
|
}
|
|
|
|
/**
|
|
* Return the CIFS helper
|
|
*
|
|
* @return CifsHelper
|
|
*/
|
|
public final CifsHelper getCifsHelper()
|
|
{
|
|
return this.cifsHelper;
|
|
}
|
|
|
|
/**
|
|
* Return the authentication service
|
|
*
|
|
* @return AuthenticationService
|
|
*/
|
|
public final AuthenticationService getAuthenticationService()
|
|
{
|
|
return authService;
|
|
}
|
|
|
|
/**
|
|
* Return the authentication context
|
|
*
|
|
* @return AuthenticationContext
|
|
*/
|
|
public final AuthenticationContext getAuthenticationContext() {
|
|
return authContext;
|
|
}
|
|
|
|
/**
|
|
* Return the node service
|
|
*
|
|
* @return NodeService
|
|
*/
|
|
public final NodeService getNodeService()
|
|
{
|
|
return this.nodeService;
|
|
}
|
|
|
|
/**
|
|
* Return the content service
|
|
*
|
|
* @return ContentService
|
|
*/
|
|
public final ContentService getContentService()
|
|
{
|
|
return this.contentService;
|
|
}
|
|
|
|
/**
|
|
* Return the namespace service
|
|
*
|
|
* @return NamespaceService
|
|
*/
|
|
public final NamespaceService getNamespaceService()
|
|
{
|
|
return this.namespaceService;
|
|
}
|
|
|
|
/**
|
|
* Return the search service
|
|
*
|
|
* @return SearchService
|
|
*/
|
|
public final SearchService getSearchService(){
|
|
return this.searchService;
|
|
}
|
|
|
|
/**
|
|
* Return the file folder service
|
|
*
|
|
* @return FileFolderService
|
|
*/
|
|
public final FileFolderService getFileFolderService() {
|
|
return this.fileFolderService;
|
|
}
|
|
|
|
/**
|
|
* Return the permission service
|
|
*
|
|
* @return PermissionService
|
|
*/
|
|
public final PermissionService getPermissionService() {
|
|
return this.permissionService;
|
|
}
|
|
|
|
/**
|
|
* Return the node archive service
|
|
*
|
|
* @param NodeArchiveService
|
|
*/
|
|
public final NodeArchiveService getNodeArchiveService() {
|
|
return nodeArchiveService;
|
|
}
|
|
|
|
/**
|
|
* Return the lock service
|
|
*
|
|
* @return LockService
|
|
*/
|
|
public final LockService getLockService() {
|
|
return lockService;
|
|
}
|
|
|
|
/**
|
|
* Get the policy behaviour filter, used to inhibit versioning on a per transaction basis
|
|
*/
|
|
public BehaviourFilter getPolicyFilter()
|
|
{
|
|
return policyBehaviourFilter;
|
|
}
|
|
|
|
/**
|
|
* Return the dictionary service
|
|
*
|
|
* @return DictionaryService
|
|
*/
|
|
public final DictionaryService getDictionaryService() {
|
|
return dictionaryService;
|
|
}
|
|
|
|
/**
|
|
* @param contentService the content service
|
|
*/
|
|
public void setContentService(ContentService contentService)
|
|
{
|
|
this.contentService = contentService;
|
|
}
|
|
|
|
/**
|
|
* @param namespaceService the namespace service
|
|
*/
|
|
public void setNamespaceService(NamespaceService namespaceService)
|
|
{
|
|
this.namespaceService = namespaceService;
|
|
}
|
|
|
|
/**
|
|
* @param nodeService the node service
|
|
*/
|
|
public void setNodeService(NodeService nodeService)
|
|
{
|
|
this.nodeService = nodeService;
|
|
}
|
|
|
|
/**
|
|
* @param searchService the search service
|
|
*/
|
|
public void setSearchService(SearchService searchService)
|
|
{
|
|
this.searchService = searchService;
|
|
}
|
|
|
|
/**
|
|
* Set the permission service
|
|
*
|
|
* @param permissionService PermissionService
|
|
*/
|
|
public void setPermissionService(PermissionService permissionService)
|
|
{
|
|
this.permissionService = permissionService;
|
|
}
|
|
|
|
/**
|
|
* Set the authentication context
|
|
*
|
|
* @param authContext AuthenticationContext
|
|
*/
|
|
public void setAuthenticationContext(AuthenticationContext authContext)
|
|
{
|
|
this.authContext = authContext;
|
|
}
|
|
|
|
/**
|
|
* Set the authentication service
|
|
*
|
|
* @param authService AuthenticationService
|
|
*/
|
|
public void setAuthenticationService(AuthenticationService authService)
|
|
{
|
|
this.authService = authService;
|
|
}
|
|
|
|
/**
|
|
* Sets the sys admin params.
|
|
*
|
|
* @param sysAdminParams
|
|
* the sys admin params
|
|
*/
|
|
public void setSysAdminParams(SysAdminParams sysAdminParams)
|
|
{
|
|
this.sysAdminParams = sysAdminParams;
|
|
}
|
|
|
|
/**
|
|
* Set the file folder service
|
|
*
|
|
* @param fileService FileFolderService
|
|
*/
|
|
public void setFileFolderService(FileFolderService fileService)
|
|
{
|
|
fileFolderService = fileService;
|
|
}
|
|
|
|
/**
|
|
* @param mimetypeService service for helping with mimetypes and encoding
|
|
*/
|
|
public void setMimetypeService(MimetypeService mimetypeService)
|
|
{
|
|
this.mimetypeService = mimetypeService;
|
|
}
|
|
|
|
/**
|
|
* Set the node monitor factory
|
|
*
|
|
* @param nodeMonitorFactory NodeMonitorFactory
|
|
*/
|
|
public void setNodeMonitorFactory(NodeMonitorFactory nodeMonitorFactory) {
|
|
m_nodeMonitorFactory = nodeMonitorFactory;
|
|
}
|
|
|
|
/**
|
|
* Set the node archive service
|
|
*
|
|
* @param NodeArchiveService nodeArchiveService
|
|
*/
|
|
public void setNodeArchiveService(NodeArchiveService nodeArchiveService) {
|
|
this.nodeArchiveService = nodeArchiveService;
|
|
}
|
|
|
|
/**
|
|
* Set the lock service
|
|
*
|
|
* @param lockService LockService
|
|
*/
|
|
public void setLockService(LockService lockService) {
|
|
this.lockService = lockService;
|
|
}
|
|
|
|
/**
|
|
* Set the policy behaviour filter, used to inhibit versioning on a per transaction basis
|
|
*
|
|
* @param policyFilter PolicyBehaviourFilter
|
|
*/
|
|
public void setPolicyFilter(BehaviourFilter policyFilter)
|
|
{
|
|
this.policyBehaviourFilter = policyFilter;
|
|
}
|
|
|
|
/**
|
|
* Set the dictionary service
|
|
*
|
|
* @param dictionaryService DictionaryService
|
|
*/
|
|
public void setDictionaryService(DictionaryService dictionaryService) {
|
|
this.dictionaryService = dictionaryService;
|
|
}
|
|
|
|
/**
|
|
* Parse and validate the parameter string and create a device context object for this instance
|
|
* of the shared device. The same DeviceInterface implementation may be used for multiple
|
|
* shares.
|
|
*
|
|
* @param shareName String
|
|
* @param args ConfigElement
|
|
* @return DeviceContext
|
|
* @exception DeviceContextException
|
|
*/
|
|
public DeviceContext createContext(String shareName, ConfigElement cfg) throws DeviceContextException
|
|
{
|
|
ContentContext context = null;
|
|
|
|
try
|
|
{
|
|
|
|
// Get the store
|
|
|
|
ConfigElement storeElement = cfg.getChild(KEY_STORE);
|
|
if (storeElement == null || storeElement.getValue() == null || storeElement.getValue().length() == 0)
|
|
{
|
|
throw new DeviceContextException("Device missing init value: " + KEY_STORE);
|
|
}
|
|
String storeValue = storeElement.getValue();
|
|
|
|
// Get the root path
|
|
|
|
ConfigElement rootPathElement = cfg.getChild(KEY_ROOT_PATH);
|
|
if (rootPathElement == null || rootPathElement.getValue() == null || rootPathElement.getValue().length() == 0)
|
|
{
|
|
throw new DeviceContextException("Device missing init value: " + KEY_ROOT_PATH);
|
|
}
|
|
String rootPath = rootPathElement.getValue();
|
|
|
|
|
|
// Create the context
|
|
|
|
context = new ContentContext();
|
|
context.setDeviceName(shareName);
|
|
context.setStoreName(storeValue);
|
|
context.setRootPath(rootPath);
|
|
context.setSysAdminParams(this.sysAdminParams);
|
|
|
|
// Check if a relative path has been specified
|
|
|
|
ConfigElement relativePathElement = cfg.getChild(KEY_RELATIVE_PATH);
|
|
|
|
if ( relativePathElement != null)
|
|
{
|
|
// Make sure the path is in CIFS format
|
|
|
|
String relPath = relativePathElement.getValue().replace( '/', FileName.DOS_SEPERATOR);
|
|
context.setRelativePath(relPath);
|
|
}
|
|
|
|
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.error("Error during create context", ex);
|
|
}
|
|
|
|
// Check if URL link files are enabled
|
|
|
|
ConfigElement urlFileElem = cfg.getChild( "urlFile");
|
|
if ( urlFileElem != null)
|
|
{
|
|
// Get the pseudo file name and web prefix path
|
|
|
|
ConfigElement pseudoName = urlFileElem.getChild( "filename");
|
|
|
|
if ( pseudoName != null)
|
|
{
|
|
context.setURLFileName(pseudoName.getValue());
|
|
}
|
|
}
|
|
|
|
// Check if locked files should be marked as offline
|
|
|
|
ConfigElement offlineFiles = cfg.getChild( "offlineFiles");
|
|
if ( offlineFiles != null)
|
|
{
|
|
context.setOfflineFiles(true);
|
|
}
|
|
|
|
// Install the node service monitor
|
|
|
|
if ( cfg.getChild("disableNodeMonitor") == null) {
|
|
|
|
// Create the node monitor
|
|
context.setDisableNodeMonitor(true);
|
|
}
|
|
|
|
// Check if oplocks are enabled, if so then enable oplocks in the lock manager
|
|
|
|
if ( cfg.getChild("disableOplocks") != null) {
|
|
context.setDisableOplocks( true);
|
|
}
|
|
|
|
// Register the device context
|
|
|
|
registerContext(context);
|
|
|
|
// Return the context for this shared filesystem
|
|
|
|
return context;
|
|
}
|
|
|
|
|
|
/**
|
|
* Registers a device context object for this instance
|
|
* of the shared device. The same DeviceInterface implementation may be used for multiple
|
|
* shares.
|
|
*
|
|
* @param ctx the context
|
|
* @exception DeviceContextException
|
|
*/
|
|
@Override
|
|
public void registerContext(DeviceContext ctx) throws DeviceContextException
|
|
{
|
|
super.registerContext(ctx);
|
|
|
|
ContentContext context = (ContentContext)ctx;
|
|
|
|
// Wrap the initialization in a transaction
|
|
|
|
UserTransaction tx = getTransactionService().getUserTransaction(true);
|
|
|
|
try
|
|
{
|
|
// Use the system user as the authenticated context for the filesystem initialization
|
|
|
|
AuthenticationUtil.pushAuthentication();
|
|
AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getSystemUserName());
|
|
|
|
// Start the transaction
|
|
|
|
if ( tx != null)
|
|
tx.begin();
|
|
|
|
// Get the store
|
|
String storeValue = context.getStoreName();
|
|
StoreRef storeRef = new StoreRef(storeValue);
|
|
|
|
// Connect to the repo and ensure that the store exists
|
|
|
|
if (! nodeService.exists(storeRef))
|
|
{
|
|
throw new DeviceContextException("Store not created prior to application startup: " + storeRef);
|
|
}
|
|
NodeRef storeRootNodeRef = nodeService.getRootNode(storeRef);
|
|
|
|
// Get the root path
|
|
String rootPath = context.getRootPath();
|
|
|
|
// Find the root node for this device
|
|
|
|
List<NodeRef> nodeRefs = searchService.selectNodes(storeRootNodeRef, rootPath, null, namespaceService, false);
|
|
|
|
NodeRef rootNodeRef = null;
|
|
|
|
if (nodeRefs.size() > 1)
|
|
{
|
|
throw new DeviceContextException("Multiple possible roots for device: \n" +
|
|
" root path: " + rootPath + "\n" +
|
|
" results: " + nodeRefs);
|
|
}
|
|
else if (nodeRefs.size() == 0)
|
|
{
|
|
// Nothing found
|
|
|
|
throw new DeviceContextException("No root found for device: \n" +
|
|
" root path: " + rootPath);
|
|
}
|
|
else
|
|
{
|
|
// We found a node
|
|
|
|
rootNodeRef = nodeRefs.get(0);
|
|
}
|
|
|
|
// Check if a relative path has been specified
|
|
|
|
String relPath = context.getRelativePath();
|
|
|
|
if ( relPath != null && relPath.length() > 0)
|
|
{
|
|
// Find the node and validate that the relative path is to a folder
|
|
|
|
NodeRef relPathNode = cifsHelper.getNodeRef( rootNodeRef, relPath);
|
|
if ( cifsHelper.isDirectory( relPathNode) == false)
|
|
throw new DeviceContextException("Relative path is not a folder, " + relPath);
|
|
|
|
// Use the relative path node as the root of the filesystem
|
|
|
|
rootNodeRef = relPathNode;
|
|
}
|
|
else {
|
|
|
|
// Make sure the default root node is a folder
|
|
|
|
if ( cifsHelper.isDirectory( rootNodeRef) == false)
|
|
throw new DeviceContextException("Root node is not a folder type node");
|
|
}
|
|
|
|
// Commit the transaction
|
|
|
|
tx.commit();
|
|
tx = null;
|
|
|
|
// Record the root node ref
|
|
context.setRootNodeRef(rootNodeRef);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.error("Error during create context", ex);
|
|
}
|
|
finally
|
|
{
|
|
// Restore authentication context
|
|
|
|
AuthenticationUtil.popAuthentication();
|
|
|
|
// If there is an active transaction then roll it back
|
|
|
|
if ( tx != null)
|
|
{
|
|
try
|
|
{
|
|
tx.rollback();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.warn("Failed to rollback transaction", ex);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check if locked files should be marked as offline
|
|
if ( context.getOfflineFiles() )
|
|
{
|
|
// Enable marking locked files as offline
|
|
|
|
cifsHelper.setMarkLockedFilesAsOffline( true);
|
|
|
|
// Logging
|
|
|
|
logger.info("Locked files will be marked as offline");
|
|
}
|
|
|
|
// Enable file state caching
|
|
|
|
context.enableStateCache( true);
|
|
context.getStateCache().setCaseSensitive( false);
|
|
|
|
// Initialize the I/O control handler
|
|
|
|
if ( context.hasIOHandler())
|
|
context.getIOHandler().initialize( this, context);
|
|
|
|
// Install the node service monitor
|
|
|
|
if ( !context.getDisableNodeMonitor() && m_nodeMonitorFactory != null) {
|
|
|
|
// Create the node monitor
|
|
|
|
NodeMonitor nodeMonitor = m_nodeMonitorFactory.createNodeMonitor( this, context);
|
|
context.setNodeMonitor( nodeMonitor);
|
|
}
|
|
|
|
// Create the lock manager
|
|
|
|
_lockManager = new FileStateLockManager( context.getStateCache());
|
|
|
|
// Check if oplocks are enabled
|
|
|
|
if ( context.getDisableOplocks() == true)
|
|
logger.warn("Oplock support disabled for filesystem " + ctx.getDeviceName());
|
|
|
|
// Start the quota manager, if enabled
|
|
|
|
if ( context.hasQuotaManager()) {
|
|
|
|
try {
|
|
|
|
// Start the quota manager
|
|
|
|
context.getQuotaManager().startManager( this, context);
|
|
logger.info("Quota manager enabled for filesystem");
|
|
}
|
|
catch ( QuotaManagerException ex) {
|
|
logger.error("Failed to start quota manager", ex);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if pseudo file support is enabled
|
|
*
|
|
* @param context ContentContext
|
|
* @return boolean
|
|
*/
|
|
public final boolean hasPseudoFileInterface(ContentContext context)
|
|
{
|
|
return context.hasPseudoFileInterface();
|
|
}
|
|
|
|
/**
|
|
* Return the pseudo file support implementation
|
|
*
|
|
* @param context ContentContext
|
|
* @return PseudoFileInterface
|
|
*/
|
|
public final PseudoFileInterface getPseudoFileInterface(ContentContext context)
|
|
{
|
|
return context.getPseudoFileInterface();
|
|
}
|
|
|
|
/**
|
|
* Determine if the disk device is read-only.
|
|
*
|
|
* @param sess Server session
|
|
* @param ctx Device context
|
|
* @return boolean
|
|
* @exception java.io.IOException If an error occurs.
|
|
*/
|
|
public boolean isReadOnly(SrvSession sess, DeviceContext ctx) throws IOException
|
|
{
|
|
if (cifsHelper.isReadOnly())
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the file information for the specified file.
|
|
*
|
|
* @param sess Server session
|
|
* @param tree Tree connection
|
|
* @param name File name/path that information is required for.
|
|
* @return File information if valid, else null
|
|
* @exception java.io.IOException The exception description.
|
|
*/
|
|
public FileInfo getFileInformation(SrvSession session, TreeConnection tree, String path) throws IOException
|
|
{
|
|
// Start a transaction
|
|
|
|
beginReadTransaction( session);
|
|
|
|
// Get the device root
|
|
|
|
ContentContext ctx = (ContentContext) tree.getContext();
|
|
NodeRef infoParentNodeRef = ctx.getRootNode();
|
|
|
|
if ( path == null || path.length() == 0)
|
|
path = FileName.DOS_SEPERATOR_STR;
|
|
|
|
String infoPath = path;
|
|
|
|
try
|
|
{
|
|
// Check if the path is to a pseudo file
|
|
|
|
FileInfo finfo = null;
|
|
|
|
if ( hasPseudoFileInterface(ctx))
|
|
{
|
|
// Make sure the parent folder has a file state, and the path exists
|
|
|
|
String[] paths = FileName.splitPath( path);
|
|
FileState fstate = ctx.getStateCache().findFileState( paths[0]);
|
|
|
|
if ( fstate == null)
|
|
{
|
|
NodeRef nodeRef = getNodeForPath(tree, paths[0]);
|
|
|
|
if ( nodeRef != null)
|
|
{
|
|
// Get the file information for the node
|
|
|
|
finfo = cifsHelper.getFileInformation(nodeRef);
|
|
}
|
|
|
|
// Create the file state
|
|
|
|
fstate = ctx.getStateCache().findFileState( paths[0], true);
|
|
|
|
fstate.setFileStatus( DirectoryExists);
|
|
fstate.setFilesystemObject( nodeRef);
|
|
|
|
// Add pseudo files to the folder
|
|
|
|
getPseudoFileInterface( ctx).addPseudoFilesToFolder( session, tree, paths[0]);
|
|
|
|
// Debug
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
|
|
logger.debug( "Added file state for pseudo files folder (getinfo) - " + paths[0]);
|
|
}
|
|
else if ( fstate.hasPseudoFiles() == false)
|
|
{
|
|
// Make sure the file state has the node ref
|
|
|
|
if ( fstate.hasFilesystemObject() == false)
|
|
{
|
|
// Get the node for the folder path
|
|
|
|
fstate.setFilesystemObject( getNodeForPath( tree, paths[0]));
|
|
}
|
|
|
|
// Add pseudo files for the parent folder
|
|
|
|
getPseudoFileInterface( ctx).addPseudoFilesToFolder( session, tree, paths[0]);
|
|
|
|
// Debug
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
|
|
logger.debug( "Added pseudo files for folder (exists) - " + paths[0]);
|
|
}
|
|
|
|
|
|
// Get the pseudo file
|
|
|
|
PseudoFile pfile = getPseudoFileInterface(ctx).getPseudoFile( session, tree, path);
|
|
if ( pfile != null)
|
|
{
|
|
// DEBUG
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
|
|
logger.debug("getInfo using pseudo file info for " + path);
|
|
|
|
FileInfo pseudoFileInfo = pfile.getFileInfo();
|
|
if (cifsHelper.isReadOnly())
|
|
{
|
|
int attr = pseudoFileInfo.getFileAttributes();
|
|
if (( attr & FileAttribute.ReadOnly) == 0)
|
|
{
|
|
attr += FileAttribute.ReadOnly;
|
|
pseudoFileInfo.setFileAttributes(attr);
|
|
}
|
|
}
|
|
return pfile.getFileInfo();
|
|
}
|
|
}
|
|
|
|
// Get the node ref for the path, chances are there is a file state in the cache
|
|
|
|
NodeRef nodeRef = getNodeForPath(tree, infoPath);
|
|
|
|
if ( nodeRef != null)
|
|
{
|
|
// Get the file information for the node
|
|
|
|
finfo = cifsHelper.getFileInformation(nodeRef);
|
|
|
|
// DEBUG
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
|
|
logger.debug("getInfo using cached noderef for path " + path);
|
|
}
|
|
|
|
// If the required node was not in the state cache, the parent folder node might be
|
|
|
|
|
|
if ( finfo == null)
|
|
{
|
|
String[] paths = FileName.splitPath( path);
|
|
|
|
if ( paths[0] != null && paths[0].length() > 1)
|
|
{
|
|
// Find the node ref for the folder being searched
|
|
|
|
nodeRef = getNodeForPath(tree, paths[0]);
|
|
|
|
if ( nodeRef != null)
|
|
{
|
|
infoParentNodeRef = nodeRef;
|
|
infoPath = paths[1];
|
|
|
|
// DEBUG
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
|
|
logger.debug("getInfo using cached noderef for parent " + path);
|
|
}
|
|
}
|
|
|
|
// Access the repository to get the file information
|
|
|
|
finfo = cifsHelper.getFileInformation(infoParentNodeRef, infoPath);
|
|
|
|
// DEBUG
|
|
|
|
if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
|
|
logger.debug("Getting file information: path=" + path + " file info: " + finfo);
|
|
}
|
|
|
|
// Set the file id for the file using the relative path
|
|
|
|
if ( finfo != null) {
|
|
|
|
// Set the file id
|
|
|
|
long id = DefaultTypeConverter.INSTANCE.convert(Long.class, nodeService.getProperty(nodeRef, ContentModel.PROP_NODE_DBID));
|
|
finfo.setFileId((int) (id & 0xFFFFFFFFL));
|
|
|
|
// Copy cached file details, if available
|
|
|
|
FileState fstate = getStateForPath(tree, infoPath);
|
|
if ( fstate != null) {
|
|
|
|
// Copy cached timestamps
|
|
|
|
if ( fstate.hasAccessDateTime())
|
|
finfo.setAccessDateTime(fstate.getAccessDateTime());
|
|
if ( fstate.hasChangeDateTime())
|
|
finfo.setChangeDateTime(fstate.getChangeDateTime());
|
|
if ( fstate.hasModifyDateTime())
|
|
finfo.setModifyDateTime(fstate.getModifyDateTime());
|
|
|
|
// File allocation size
|
|
|
|
if ( fstate.hasAllocationSize() && fstate.getAllocationSize() > finfo.getSize())
|
|
finfo.setAllocationSize( fstate.getAllocationSize());
|
|
}
|
|
else {
|
|
|
|
// Create a file state for the file/folder
|
|
|
|
fstate = ctx.getStateCache().findFileState( path, true);
|
|
if ( finfo.isDirectory())
|
|
fstate.setFileStatus( DirectoryExists);
|
|
else
|
|
fstate.setFileStatus( FileExists);
|
|
fstate.setFilesystemObject( nodeRef);
|
|
}
|
|
}
|
|
|
|
// Return the file information
|
|
|
|
return finfo;
|
|
}
|
|
catch (FileNotFoundException e)
|
|
{
|
|
// Debug
|
|
|
|
if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
|
|
logger.debug("Get file info - file not found, " + path);
|
|
throw e;
|
|
}
|
|
catch (org.alfresco.repo.security.permissions.AccessDeniedException ex)
|
|
{
|
|
// Debug
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
|
|
logger.debug("Get file info - access denied, " + path);
|
|
|
|
// Convert to a filesystem access denied status
|
|
|
|
throw new AccessDeniedException("Get file information " + path);
|
|
}
|
|
catch (AlfrescoRuntimeException ex)
|
|
{
|
|
// Debug
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
|
|
logger.debug("Get file info error", ex);
|
|
|
|
// Convert to a general I/O exception
|
|
|
|
throw new IOException("Get file information " + path);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Start a new search on the filesystem using the specified searchPath that may contain
|
|
* wildcards.
|
|
*
|
|
* @param sess Server session
|
|
* @param tree Tree connection
|
|
* @param searchPath File(s) to search for, may include wildcards.
|
|
* @param attrib Attributes of the file(s) to search for, see class SMBFileAttribute.
|
|
* @return SearchContext
|
|
* @exception java.io.FileNotFoundException If the search could not be started.
|
|
*/
|
|
public SearchContext startSearch(SrvSession sess, TreeConnection tree, String searchPath, int attributes) throws FileNotFoundException
|
|
{
|
|
// Access the device context
|
|
|
|
ContentContext ctx = (ContentContext) tree.getContext();
|
|
|
|
try
|
|
{
|
|
String searchFileSpec = searchPath;
|
|
NodeRef searchRootNodeRef = ctx.getRootNode();
|
|
FileState searchFolderState = null;
|
|
|
|
// Create the transaction
|
|
|
|
beginReadTransaction( sess);
|
|
|
|
// If the state table is available see if we can speed up the search using either cached
|
|
// file information or find the folder node to be searched without having to walk the path
|
|
|
|
String[] paths = FileName.splitPath(searchPath);
|
|
|
|
if ( ctx.hasStateCache())
|
|
{
|
|
// See if the folder to be searched has a file state, we can avoid having to walk the path
|
|
|
|
if ( paths[0] != null && paths[0].length() >= 1)
|
|
{
|
|
// Find the node ref for the folder being searched
|
|
|
|
NodeRef nodeRef = getNodeForPath(tree, paths[0]);
|
|
|
|
// Get the file state for the folder being searched
|
|
|
|
searchFolderState = getStateForPath(tree, paths[0]);
|
|
if ( searchFolderState == null)
|
|
{
|
|
// Create a file state for the folder
|
|
|
|
searchFolderState = ctx.getStateCache().findFileState( paths[0], true);
|
|
}
|
|
|
|
// Make sure the associated node is set
|
|
|
|
if ( searchFolderState.hasFilesystemObject() == false)
|
|
{
|
|
// Set the associated node for the folder
|
|
|
|
searchFolderState.setFilesystemObject( nodeRef);
|
|
}
|
|
|
|
// Add pseudo files to the folder being searched
|
|
|
|
if ( hasPseudoFileInterface(ctx))
|
|
getPseudoFileInterface(ctx).addPseudoFilesToFolder( sess, tree, paths[0]);
|
|
|
|
// Set the search node and file spec
|
|
|
|
if ( nodeRef != null)
|
|
{
|
|
searchRootNodeRef = nodeRef;
|
|
searchFileSpec = paths[1];
|
|
|
|
// DEBUG
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_SEARCH))
|
|
logger.debug("Search using cached noderef for path " + searchPath);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Convert the all files wildcard
|
|
|
|
if ( searchFileSpec.equals( "*.*"))
|
|
searchFileSpec = "*";
|
|
|
|
// Debug
|
|
|
|
long startTime = 0L;
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_SEARCH))
|
|
startTime = System.currentTimeMillis();
|
|
|
|
// Perform the search
|
|
|
|
List<NodeRef> results = cifsHelper.getNodeRefs(searchRootNodeRef, searchFileSpec);
|
|
|
|
// Debug
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_SEARCH)) {
|
|
long endTime = System.currentTimeMillis();
|
|
if (( endTime - startTime) > 500)
|
|
logger.debug("Search for searchPath=" + searchPath + ", searchSpec=" + searchFileSpec + ", searchRootNode=" + searchRootNodeRef + " took "
|
|
+ ( endTime - startTime) + "ms results=" + results.size());
|
|
}
|
|
|
|
// Check if there are any pseudo files for the folder being searched, for CIFS only
|
|
|
|
PseudoFileList pseudoList = null;
|
|
|
|
if ( sess instanceof SMBSrvSession && searchFolderState != null && searchFolderState.hasPseudoFiles())
|
|
{
|
|
// If it is a wildcard search use all pseudo files
|
|
|
|
if ( WildCard.containsWildcards(searchFileSpec))
|
|
{
|
|
// Get the list of pseudo files for the search path
|
|
|
|
pseudoList = searchFolderState.getPseudoFileList();
|
|
|
|
// Check if the wildcard is for all files or a subset
|
|
|
|
if ( searchFileSpec.equals( "*") == false && pseudoList != null && pseudoList.numberOfFiles() > 0)
|
|
{
|
|
// Generate a subset of pseudo files that match the wildcard search pattern
|
|
|
|
WildCard wildCard = new WildCard( searchFileSpec, false);
|
|
PseudoFileList filterList = null;
|
|
|
|
for ( int i = 0; i > pseudoList.numberOfFiles(); i++)
|
|
{
|
|
PseudoFile pseudoFile = pseudoList.getFileAt( i);
|
|
if ( wildCard.matchesPattern( pseudoFile.getFileName()))
|
|
{
|
|
// Add the pseudo file to the filtered list
|
|
|
|
if ( filterList == null)
|
|
filterList = new PseudoFileList();
|
|
filterList.addFile( pseudoFile);
|
|
}
|
|
}
|
|
|
|
// Use the filtered pseudo file list, or null if there were no matches
|
|
|
|
pseudoList = filterList;
|
|
}
|
|
}
|
|
else if ( results == null || results.size() == 0)
|
|
{
|
|
// Check if the required file is in the pseudo file list
|
|
|
|
String fname = paths[1];
|
|
|
|
if ( fname != null)
|
|
{
|
|
// Search for a matching pseudo file
|
|
|
|
PseudoFile pfile = searchFolderState.getPseudoFileList().findFile( fname, true);
|
|
if ( pfile != null)
|
|
{
|
|
// Create a file list with the required file
|
|
|
|
pseudoList = new PseudoFileList();
|
|
pseudoList.addFile( pfile);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Build the search context to store the results, use the cache lookup search for wildcard searches
|
|
|
|
SearchContext searchCtx = null;
|
|
|
|
if ( searchFileSpec.equals( "*"))
|
|
{
|
|
// Use a cache lookup search context
|
|
|
|
CacheLookupSearchContext cacheContext = new CacheLookupSearchContext(cifsHelper, results, searchFileSpec, pseudoList, paths[0], ctx.getStateCache());
|
|
searchCtx = cacheContext;
|
|
|
|
// Set the '.' and '..' pseudo file entry details
|
|
|
|
if ( searchFolderState != null && searchFolderState.hasFilesystemObject())
|
|
{
|
|
// Get the '.' pseudo entry file details
|
|
|
|
FileInfo finfo = cifsHelper.getFileInformation((NodeRef) searchFolderState.getFilesystemObject());
|
|
|
|
// Blend in any cached timestamps
|
|
|
|
if ( searchFolderState != null) {
|
|
if ( searchFolderState.hasAccessDateTime())
|
|
finfo.setAccessDateTime( searchFolderState.getAccessDateTime());
|
|
|
|
if ( searchFolderState.hasChangeDateTime())
|
|
finfo.setChangeDateTime( searchFolderState.getChangeDateTime());
|
|
|
|
if ( searchFolderState.hasModifyDateTime())
|
|
finfo.setModifyDateTime( searchFolderState.getModifyDateTime());
|
|
}
|
|
|
|
// Set the '.' pseudo entry details
|
|
|
|
cacheContext.setDotInfo( finfo);
|
|
|
|
// Check if the search folder has a parent, if we are at the root of the filesystem then re-use
|
|
// the file information
|
|
|
|
if ( searchFolderState.getPath().equals( FileName.DOS_SEPERATOR_STR)) {
|
|
|
|
// Searching the root folder, re-use the search folder file information for the '..' pseudo entry
|
|
|
|
cacheContext.setDotDotInfo( finfo);
|
|
}
|
|
else {
|
|
|
|
// Get the parent folder path
|
|
|
|
String parentPath = searchFolderState.getPath();
|
|
if ( parentPath.endsWith( FileName.DOS_SEPERATOR_STR) && parentPath.length() > 1)
|
|
parentPath = parentPath.substring(0, parentPath.length() - 1);
|
|
|
|
int pos = parentPath.lastIndexOf( FileName.DOS_SEPERATOR_STR);
|
|
if ( pos != -1)
|
|
parentPath = parentPath.substring(0, pos + 1);
|
|
|
|
// Get the file state for the parent path, if available
|
|
|
|
FileState parentState = ctx.getStateCache().findFileState( parentPath);
|
|
NodeRef parentNode = null;
|
|
|
|
if ( parentState != null)
|
|
parentNode = (NodeRef) parentState.getFilesystemObject();
|
|
|
|
if ( parentState == null || parentNode == null)
|
|
parentNode = getNodeForPath( tree, parentPath);
|
|
|
|
// Get the file information for the parent folder
|
|
|
|
finfo = cifsHelper.getFileInformation( parentNode);
|
|
|
|
// Blend in any cached timestamps
|
|
|
|
if ( parentState != null) {
|
|
if ( parentState.hasAccessDateTime())
|
|
finfo.setAccessDateTime( parentState.getAccessDateTime());
|
|
|
|
if ( parentState.hasChangeDateTime())
|
|
finfo.setChangeDateTime( parentState.getChangeDateTime());
|
|
|
|
if ( parentState.hasModifyDateTime())
|
|
finfo.setModifyDateTime( parentState.getModifyDateTime());
|
|
}
|
|
|
|
// Set the '..' pseudo entry details
|
|
|
|
cacheContext.setDotDotInfo( finfo);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
searchCtx = new ContentSearchContext(cifsHelper, results, searchFileSpec, pseudoList, paths[0]);
|
|
|
|
// Debug
|
|
|
|
if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_SEARCH))
|
|
logger.debug("Started search: search path=" + searchPath + " attributes=" + attributes + ", ctx=" + searchCtx);
|
|
|
|
// Return the search context
|
|
|
|
return searchCtx;
|
|
}
|
|
catch (org.alfresco.repo.security.permissions.AccessDeniedException ex)
|
|
{
|
|
// Debug
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_SEARCH))
|
|
logger.debug("Start search - access denied, " + searchPath);
|
|
|
|
// Convert to a file not found status
|
|
|
|
throw new FileNotFoundException("Start search " + searchPath);
|
|
}
|
|
catch (AlfrescoRuntimeException ex)
|
|
{
|
|
// Debug
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_SEARCH))
|
|
logger.debug("Start search", ex);
|
|
|
|
// Convert to a file not found status
|
|
|
|
throw new FileNotFoundException("Start search " + searchPath);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if the specified file exists, and whether it is a file or directory.
|
|
*
|
|
* @param sess Server session
|
|
* @param tree Tree connection
|
|
* @param name java.lang.String
|
|
* @return int
|
|
* @see FileStatus
|
|
*/
|
|
public int fileExists(SrvSession sess, TreeConnection tree, String name)
|
|
{
|
|
ContentContext ctx = (ContentContext) tree.getContext();
|
|
int status = FileStatus.Unknown;
|
|
FileState fstate = null;
|
|
|
|
try
|
|
{
|
|
// Check for a cached file state
|
|
|
|
if ( ctx.hasStateCache())
|
|
fstate = ctx.getStateCache().findFileState(name, true);
|
|
|
|
if ( fstate != null && fstate.getFileStatus() != FileUnknown)
|
|
{
|
|
status = fstate.getFileStatus();
|
|
if ( status >= CustomFileStatus)
|
|
status = FileNotExist;
|
|
|
|
// DEBUG
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
|
|
logger.debug("Cache hit - fileExists() " + name + ", sts=" + status);
|
|
}
|
|
else
|
|
{
|
|
// Check if pseudo files are enabled
|
|
|
|
if ( hasPseudoFileInterface(ctx))
|
|
{
|
|
// Check if the file name is a pseudo file name
|
|
|
|
if ( getPseudoFileInterface( ctx).isPseudoFile(sess, tree, name)) {
|
|
|
|
// Make sure the parent folder has a file state, and the path exists
|
|
|
|
String[] paths = FileName.splitPath( name);
|
|
fstate = ctx.getStateCache().findFileState( paths[0]);
|
|
|
|
if ( fstate == null) {
|
|
|
|
// Check if the path exists
|
|
|
|
if ( fileExists( sess, tree, paths[0]) == FileStatus.DirectoryExists)
|
|
{
|
|
// Create the file state
|
|
|
|
fstate = ctx.getStateCache().findFileState( paths[0], true);
|
|
|
|
fstate.setFileStatus( DirectoryExists);
|
|
|
|
// Get the node for the folder path
|
|
|
|
beginReadTransaction( sess);
|
|
fstate.setFilesystemObject( getNodeForPath( tree, paths[0]));
|
|
|
|
// Add pseudo files to the folder
|
|
|
|
getPseudoFileInterface( ctx).addPseudoFilesToFolder( sess, tree, paths[0]);
|
|
|
|
// Debug
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_PSEUDO))
|
|
logger.debug( "Added file state for pseudo files folder (exists) - " + paths[0]);
|
|
}
|
|
}
|
|
else if ( fstate.hasPseudoFiles() == false)
|
|
{
|
|
// Make sure the file state has the node ref
|
|
|
|
if ( fstate.hasFilesystemObject() == false)
|
|
{
|
|
// Create the transaction
|
|
|
|
beginReadTransaction( sess);
|
|
|
|
// Get the node for the folder path
|
|
|
|
fstate.setFilesystemObject( getNodeForPath( tree, paths[0]));
|
|
}
|
|
|
|
// Add pseudo files for the parent folder
|
|
|
|
getPseudoFileInterface( ctx).addPseudoFilesToFolder( sess, tree, paths[0]);
|
|
|
|
// Debug
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_PSEUDO))
|
|
logger.debug( "Added pseudo files for folder (exists) - " + paths[0]);
|
|
}
|
|
|
|
// Check if the path is to a pseudo file
|
|
|
|
PseudoFile pfile = getPseudoFileInterface(ctx).getPseudoFile( sess, tree, name);
|
|
if ( pfile != null)
|
|
{
|
|
// Indicate that the file exists
|
|
|
|
status = FileStatus.FileExists;
|
|
}
|
|
else
|
|
{
|
|
// Failed to find pseudo file
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_PSEUDO))
|
|
logger.debug( "Failed to find pseudo file (exists) - " + name);
|
|
}
|
|
}
|
|
}
|
|
|
|
// If the file is not a pseudo file then search for the file
|
|
|
|
if ( status == FileStatus.Unknown)
|
|
{
|
|
// Create the transaction
|
|
|
|
beginReadTransaction( sess);
|
|
|
|
// Get the file information to check if the file/folder exists
|
|
|
|
FileInfo info = getFileInformation(sess, tree, name);
|
|
if (info.isDirectory())
|
|
{
|
|
status = FileStatus.DirectoryExists;
|
|
}
|
|
else
|
|
{
|
|
status = FileStatus.FileExists;
|
|
}
|
|
|
|
// Update the file state status
|
|
|
|
if ( fstate != null)
|
|
fstate.setFileStatus( status);
|
|
}
|
|
}
|
|
}
|
|
catch (FileNotFoundException e)
|
|
{
|
|
status = FileStatus.NotExist;
|
|
if ( fstate != null)
|
|
fstate.setFileStatus( status);
|
|
}
|
|
catch (IOException e)
|
|
{
|
|
// Debug
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
|
|
logger.debug("File exists error, " + name, e);
|
|
|
|
status = FileStatus.NotExist;
|
|
}
|
|
|
|
// Debug
|
|
|
|
if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
|
|
logger.debug("File status determined: name=" + name + " status=" + fileStatusString(fstate.getFileStatus()));
|
|
|
|
// Return the file/folder status
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* Open a file or folder
|
|
*
|
|
* @param sess SrvSession
|
|
* @param tree TreeConnection
|
|
* @param params FileOpenParams
|
|
* @return NetworkFile
|
|
* @exception IOException
|
|
*/
|
|
public NetworkFile openFile(SrvSession sess, TreeConnection tree, FileOpenParams params) throws IOException
|
|
{
|
|
// Create the transaction
|
|
|
|
beginReadTransaction( sess);
|
|
ContentContext ctx = (ContentContext) tree.getContext();
|
|
|
|
try
|
|
{
|
|
// Check if pseudo files are enabled
|
|
|
|
if ( hasPseudoFileInterface(ctx))
|
|
{
|
|
// Check if the file name is a pseudo file name
|
|
|
|
String path = params.getPath();
|
|
|
|
if ( getPseudoFileInterface( ctx).isPseudoFile(sess, tree, path)) {
|
|
|
|
// Make sure the parent folder has a file state, and the path exists
|
|
|
|
String[] paths = FileName.splitPath( path);
|
|
FileState fstate = ctx.getStateCache().findFileState( paths[0]);
|
|
|
|
if ( fstate == null) {
|
|
|
|
// Check if the path exists
|
|
|
|
if ( fileExists( sess, tree, paths[0]) == FileStatus.DirectoryExists)
|
|
{
|
|
// Create the file state and add any pseudo files
|
|
|
|
fstate = ctx.getStateCache().findFileState( paths[0], true);
|
|
|
|
fstate.setFileStatus( DirectoryExists);
|
|
getPseudoFileInterface( ctx).addPseudoFilesToFolder( sess, tree, paths[0]);
|
|
|
|
// Debug
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_PSEUDO))
|
|
logger.debug( "Added file state for pseudo files folder (open) - " + paths[0]);
|
|
}
|
|
}
|
|
else if ( fstate.hasPseudoFiles() == false)
|
|
{
|
|
// Add pseudo files for the parent folder
|
|
|
|
getPseudoFileInterface( ctx).addPseudoFilesToFolder( sess, tree, paths[0]);
|
|
|
|
// Debug
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_PSEUDO))
|
|
logger.debug( "Added pseudo files for folder (open) - " + paths[0]);
|
|
}
|
|
|
|
// Check if the path is to a pseudo file
|
|
|
|
PseudoFile pfile = getPseudoFileInterface(ctx).getPseudoFile( sess, tree, params.getPath());
|
|
if ( pfile != null)
|
|
{
|
|
// Create a network file to access the pseudo file data
|
|
|
|
return pfile.getFile( params.getPath());
|
|
}
|
|
else
|
|
{
|
|
// Failed to find pseudo file
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_PSEUDO))
|
|
logger.debug( "Failed to find pseudo file (open) - " + params.getPath());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Not a pseudo file, try and open a normal file/folder node
|
|
|
|
NodeRef nodeRef = getNodeForPath(tree, params.getPath());
|
|
|
|
// Check permissions on the file/folder node
|
|
//
|
|
// Check for read access
|
|
|
|
if ( params.hasAccessMode(AccessMode.NTRead) &&
|
|
permissionService.hasPermission(nodeRef, PermissionService.READ) == AccessStatus.DENIED)
|
|
throw new AccessDeniedException("No read access to " + params.getFullPath());
|
|
|
|
// Check for write access
|
|
|
|
if ( params.hasAccessMode(AccessMode.NTWrite) &&
|
|
permissionService.hasPermission(nodeRef, PermissionService.WRITE) == AccessStatus.DENIED)
|
|
throw new AccessDeniedException("No write access to " + params.getFullPath());
|
|
|
|
// Check for delete access
|
|
|
|
// if ( params.hasAccessMode(AccessMode.NTDelete) &&
|
|
// permissionService.hasPermission(nodeRef, PermissionService.DELETE) == AccessStatus.DENIED)
|
|
// throw new AccessDeniedException("No delete access to " + params.getFullPath());
|
|
|
|
// Check if the file has a lock
|
|
|
|
String lockTypeStr = (String) nodeService.getProperty( nodeRef, ContentModel.PROP_LOCK_TYPE);
|
|
|
|
if ( params.hasAccessMode(AccessMode.NTWrite) && lockTypeStr != null)
|
|
throw new AccessDeniedException("File is locked, no write access to " + params.getFullPath());
|
|
|
|
// Check if there is a file state for the file
|
|
|
|
FileState fstate = null;
|
|
|
|
if ( ctx.hasStateCache())
|
|
{
|
|
// Check if there is a file state for the file
|
|
|
|
fstate = ctx.getStateCache().findFileState( params.getPath());
|
|
|
|
if ( fstate != null)
|
|
{
|
|
// Check if the file exists
|
|
|
|
if ( fstate.exists() == false)
|
|
throw new FileNotFoundException();
|
|
}
|
|
else {
|
|
|
|
// Create a file state for the path
|
|
|
|
fstate = ctx.getStateCache().findFileState( params.getPath(), true);
|
|
}
|
|
|
|
// Check if the current file open allows the required shared access
|
|
|
|
boolean nosharing = false;
|
|
|
|
// TEST
|
|
|
|
if ( params.getAccessMode() == AccessMode.NTFileGenericExecute && params.getPath().toLowerCase().endsWith( ".exe") == false) {
|
|
|
|
// DEBUG
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE)) {
|
|
logger.debug( "Execute access mode, path" + params.getPath());
|
|
logger.debug( " Fstate=" + fstate);
|
|
}
|
|
|
|
throw new AccessDeniedException("Invalid access mode");
|
|
}
|
|
|
|
if ( fstate.getOpenCount() > 0) {
|
|
|
|
// Check for impersonation security level from the original process that opened the file
|
|
|
|
if ( params.getSecurityLevel() == WinNT.SecurityImpersonation && params.getProcessId() == fstate.getProcessId())
|
|
nosharing = false;
|
|
|
|
// Check if the caller wants read access, check the sharing mode
|
|
|
|
else if ( params.isReadOnlyAccess() && (fstate.getSharedAccess() & SharingMode.READ) != 0)
|
|
nosharing = false;
|
|
|
|
// Check if the caller wants write access, check the sharing mode
|
|
|
|
else if (( params.isReadWriteAccess() || params.isWriteOnlyAccess()) && (fstate.getSharedAccess() & SharingMode.WRITE) == 0)
|
|
{
|
|
// DEBUG
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
|
|
logger.debug("Sharing mode disallows write access path=" + params.getPath());
|
|
|
|
// Access not allowed
|
|
|
|
throw new AccessDeniedException( "Sharing mode (write)");
|
|
}
|
|
|
|
// Check if the file has been opened for exclusive access
|
|
|
|
else if ( fstate.getSharedAccess() == SharingMode.NOSHARING)
|
|
nosharing = true;
|
|
|
|
// Check if the required sharing mode is allowed by the current file open
|
|
|
|
else if ( ( fstate.getSharedAccess() & params.getSharedAccess()) != params.getSharedAccess())
|
|
nosharing = true;
|
|
|
|
// Check if the caller wants exclusive access to the file
|
|
|
|
else if ( params.getSharedAccess() == SharingMode.NOSHARING)
|
|
nosharing = true;
|
|
|
|
}
|
|
|
|
// Check if the file allows shared access
|
|
|
|
if ( nosharing == true)
|
|
{
|
|
if ( params.getPath().equals( "\\") == false) {
|
|
|
|
// DEBUG
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
|
|
logger.debug("Sharing violation path=" + params.getPath() + ", sharing=0x" + Integer.toHexString(fstate.getSharedAccess()));
|
|
|
|
// File is locked by another user
|
|
|
|
throw new FileSharingException("File already open, " + params.getPath());
|
|
}
|
|
}
|
|
|
|
// Update the file sharing mode and process id, if this is the first file open
|
|
|
|
fstate.setSharedAccess( params.getSharedAccess());
|
|
fstate.setProcessId( params.getProcessId());
|
|
|
|
// DEBUG
|
|
|
|
if ( logger.isDebugEnabled() && fstate.getOpenCount() == 0 && ctx.hasDebug(AlfrescoContext.DBG_FILE))
|
|
logger.debug("Path " + params.getPath() + ", sharing=0x" + Integer.toHexString(params.getSharedAccess()) + ", PID=" + params.getProcessId());
|
|
}
|
|
|
|
// Check if the node is a link node
|
|
|
|
NodeRef linkRef = (NodeRef) nodeService.getProperty(nodeRef, ContentModel.PROP_LINK_DESTINATION);
|
|
AlfrescoNetworkFile netFile = null;
|
|
|
|
if ( linkRef == null)
|
|
{
|
|
// Check if the file is already opened by this client/process
|
|
|
|
if ( tree.openFileCount() > 0) {
|
|
|
|
// Search the open file table for this session/virtual circuit
|
|
|
|
int idx = 0;
|
|
|
|
while ( idx < tree.getFileTableLength() && netFile == null) {
|
|
|
|
// Get the current file from the open file table
|
|
|
|
NetworkFile curFile = tree.findFile( idx);
|
|
if ( curFile != null && curFile instanceof ContentNetworkFile) {
|
|
|
|
// Check if the file is the same path and process id
|
|
|
|
ContentNetworkFile contentFile = (ContentNetworkFile) curFile;
|
|
if ( contentFile.getProcessId() == params.getProcessId() &&
|
|
contentFile.getFullName().equalsIgnoreCase( params.getFullPath())) {
|
|
|
|
// Check that the access mode is the same
|
|
|
|
if (( params.isReadWriteAccess() && contentFile.getGrantedAccess() == NetworkFile.READWRITE) ||
|
|
( params.isReadOnlyAccess() && contentFile.getGrantedAccess() == NetworkFile.READONLY)) {
|
|
|
|
// Found a match, re-use the open file
|
|
|
|
netFile = contentFile;
|
|
|
|
// Increment the file open count, last file close will actually close the file/stream
|
|
|
|
contentFile.incrementOpenCount();
|
|
|
|
// DEBUG
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
|
|
logger.debug("Re-use existing file open Path " + params.getPath() + ", PID=" + params.getProcessId() + ", params=" +
|
|
( params.isReadOnlyAccess() ? "ReadOnly" : "Write") + ", file=" +
|
|
( contentFile.getGrantedAccess() == NetworkFile.READONLY ? "ReadOnly" : "Write"));
|
|
}
|
|
else if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
|
|
logger.debug("Not re-using file path=" + params.getPath() + ", readWrite=" + (params.isReadWriteAccess() ? "true" : "false") +
|
|
", readOnly=" + (params.isReadOnlyAccess() ? "true" : "false") +
|
|
", grantedAccess=" + contentFile.getGrantedAccessAsString());
|
|
}
|
|
}
|
|
|
|
// Update the file table index
|
|
|
|
idx++;
|
|
}
|
|
}
|
|
|
|
// Create the network file, if we could not match an existing file open
|
|
|
|
if ( netFile == null) {
|
|
|
|
// Create a new network file for the open request
|
|
|
|
netFile = ContentNetworkFile.createFile(nodeService, contentService, mimetypeService, cifsHelper, nodeRef, params, sess);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Get the CIFS server name
|
|
|
|
String srvName = null;
|
|
SMBServer cifsServer = (SMBServer) sess.getServer().getConfiguration().findServer( "CIFS");
|
|
|
|
if ( cifsServer != null)
|
|
{
|
|
// Use the CIFS server name in the URL
|
|
|
|
srvName = cifsServer.getServerName();
|
|
}
|
|
else
|
|
{
|
|
// Use the local server name in the URL
|
|
|
|
srvName = InetAddress.getLocalHost().getHostName();
|
|
}
|
|
|
|
// Convert the target node to a path, convert to URL format
|
|
|
|
String path = getPathForNode( tree, linkRef);
|
|
path = path.replace( FileName.DOS_SEPERATOR, '/');
|
|
|
|
// Build the URL file data
|
|
|
|
StringBuilder urlStr = new StringBuilder();
|
|
|
|
urlStr.append("[InternetShortcut]\r\n");
|
|
urlStr.append("URL=file://");
|
|
urlStr.append( srvName);
|
|
urlStr.append("/");
|
|
urlStr.append( tree.getSharedDevice().getName());
|
|
urlStr.append( path);
|
|
urlStr.append("\r\n");
|
|
|
|
// Create the in memory pseudo file for the URL link
|
|
|
|
byte[] urlData = urlStr.toString().getBytes();
|
|
|
|
// Get the file information for the link node
|
|
|
|
FileInfo fInfo = cifsHelper.getFileInformation( nodeRef);
|
|
|
|
// Set the file size to the actual data length
|
|
|
|
fInfo.setFileSize( urlData.length);
|
|
|
|
// Create the network file using the in-memory file data
|
|
|
|
netFile = new LinkMemoryNetworkFile( fInfo.getFileName(), urlData, fInfo, nodeRef);
|
|
netFile.setFullName( params.getPath());
|
|
}
|
|
|
|
// Generate a file id for the file
|
|
|
|
if ( netFile != null) {
|
|
long id = DefaultTypeConverter.INSTANCE.convert(Long.class, nodeService.getProperty(nodeRef, ContentModel.PROP_NODE_DBID));
|
|
netFile.setFileId(( int) ( id & 0xFFFFFFFFL));
|
|
}
|
|
|
|
// If the file has been opened for overwrite then truncate the file to zero length, this will
|
|
// also prevent the existing content data from being copied to the new version of the file
|
|
|
|
if ( params.isOverwrite() && netFile != null)
|
|
{
|
|
// Truncate the file to zero length
|
|
|
|
netFile.truncateFile( 0L);
|
|
}
|
|
|
|
// Create a file state for the open file
|
|
|
|
if ( ctx.hasStateCache())
|
|
{
|
|
if ( fstate == null)
|
|
fstate = ctx.getStateCache().findFileState(params.getPath(), true);
|
|
|
|
// Update the file state, cache the node
|
|
|
|
fstate.incrementOpenCount();
|
|
fstate.setFilesystemObject(nodeRef);
|
|
|
|
// Store the state with the file
|
|
|
|
netFile.setFileState( fstate);
|
|
|
|
// Set the file access date/time, if available
|
|
|
|
if ( fstate.hasAccessDateTime())
|
|
netFile.setAccessDate( fstate.getAccessDateTime());
|
|
}
|
|
|
|
// Debug
|
|
|
|
if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
|
|
logger.debug("Opened network file: path=" + params.getPath() + " file open parameters=" + params + " network file=" + netFile);
|
|
|
|
// Return the network file
|
|
|
|
return netFile;
|
|
}
|
|
catch (org.alfresco.repo.security.permissions.AccessDeniedException ex)
|
|
{
|
|
// Debug
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
|
|
logger.debug("Open file - access denied, " + params.getFullPath());
|
|
|
|
// Convert to a filesystem access denied status
|
|
|
|
throw new AccessDeniedException("Open file " + params.getFullPath());
|
|
}
|
|
catch (AlfrescoRuntimeException ex)
|
|
{
|
|
// Debug
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
|
|
logger.debug("Open file error", ex);
|
|
|
|
// Convert to a general I/O exception
|
|
|
|
throw new IOException("Open file " + params.getFullPath());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a new file on the file system.
|
|
*
|
|
* @param sess Server session
|
|
* @param tree Tree connection
|
|
* @param params File create parameters
|
|
* @return NetworkFile
|
|
* @exception java.io.IOException If an error occurs.
|
|
*/
|
|
public NetworkFile createFile(SrvSession sess, final TreeConnection tree, final FileOpenParams params) throws IOException
|
|
{
|
|
final ContentContext ctx = (ContentContext) tree.getContext();
|
|
|
|
try
|
|
{
|
|
|
|
// Access the repository in a retryable write transaction
|
|
Pair<String, NodeRef> result = doInWriteTransaction(sess, new CallableIO<Pair<String, NodeRef>>(){
|
|
public Pair<String, NodeRef> call() throws IOException
|
|
{
|
|
// Get the device root
|
|
|
|
NodeRef deviceRootNodeRef = ctx.getRootNode();
|
|
|
|
String path = params.getPath();
|
|
String parentPath = null;
|
|
|
|
// If the state table is available then try to find the parent folder node for the new file
|
|
// to save having to walk the path
|
|
|
|
if (ctx.hasStateCache())
|
|
{
|
|
// See if the parent folder has a file state, we can avoid having to walk the path
|
|
|
|
String[] paths = FileName.splitPath(path);
|
|
if ( paths[0] != null && paths[0].length() > 1)
|
|
{
|
|
// Find the node ref for the folder being searched
|
|
|
|
NodeRef nodeRef = getNodeForPath(tree, paths[0]);
|
|
|
|
if ( nodeRef != null)
|
|
{
|
|
deviceRootNodeRef = nodeRef;
|
|
path = paths[1];
|
|
|
|
// DEBUG
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
|
|
logger.debug("Create file using cached noderef for path " + paths[0]);
|
|
}
|
|
|
|
parentPath = paths[0];
|
|
}
|
|
}
|
|
|
|
// Create it - the path will be created, if necessary
|
|
|
|
NodeRef nodeRef = cifsHelper.createNode(deviceRootNodeRef, path, ContentModel.TYPE_CONTENT);
|
|
nodeService.addAspect(nodeRef, ContentModel.ASPECT_NO_CONTENT, null);
|
|
|
|
return new Pair<String, NodeRef>(parentPath, nodeRef);
|
|
}});
|
|
|
|
// Get or create the file state for the parent folder
|
|
FileState parentState = null;
|
|
String parentPath = result.getFirst();
|
|
if (parentPath != null)
|
|
{
|
|
parentState = getStateForPath(tree, parentPath);
|
|
if (parentState == null && ctx.hasStateCache())
|
|
parentState = ctx.getStateCache().findFileState(parentPath, true);
|
|
}
|
|
|
|
// Create the network file
|
|
|
|
ContentNetworkFile netFile = ContentNetworkFile.createFile(nodeService, contentService, mimetypeService, cifsHelper, result.getSecond(), params, sess);
|
|
|
|
// Always allow write access to a newly created file
|
|
|
|
netFile.setGrantedAccess(NetworkFile.READWRITE);
|
|
|
|
// Set the owner process id for this open file
|
|
|
|
netFile.setProcessId( params.getProcessId());
|
|
|
|
// Truncate the file so that the content stream is created
|
|
|
|
netFile.truncateFile( 0L);
|
|
|
|
// Generate a file id for the file
|
|
|
|
if ( netFile != null) {
|
|
long id = DefaultTypeConverter.INSTANCE.convert(Long.class, nodeService.getProperty(netFile.getNodeRef(), ContentModel.PROP_NODE_DBID));
|
|
netFile.setFileId((int) (id & 0xFFFFFFFFL));
|
|
}
|
|
|
|
// Add a file state for the new file/folder
|
|
|
|
if ( ctx.hasStateCache())
|
|
{
|
|
FileState fstate = ctx.getStateCache().findFileState(params.getPath(), true);
|
|
if ( fstate != null)
|
|
{
|
|
// Save the file sharing mode, needs to be done before the open count is incremented
|
|
|
|
fstate.setSharedAccess( params.getSharedAccess());
|
|
fstate.setProcessId( params.getProcessId());
|
|
|
|
// Indicate that the file is open
|
|
|
|
fstate.setFileStatus( FileExists);
|
|
fstate.incrementOpenCount();
|
|
fstate.setFilesystemObject(result.getSecond());
|
|
|
|
// Track the intial allocation size
|
|
|
|
fstate.setAllocationSize( params.getAllocationSize());
|
|
|
|
// Store the file state with the file
|
|
|
|
netFile.setFileState( fstate);
|
|
|
|
// DEBUG
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
|
|
logger.debug("Create file, state=" + fstate);
|
|
}
|
|
|
|
// Update the parent folder file state
|
|
|
|
if ( parentState != null)
|
|
parentState.updateModifyDateTime();
|
|
}
|
|
|
|
// Debug
|
|
|
|
if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
|
|
logger.debug("Created file: path=" + params.getPath() + " file open parameters=" + params + " node=" + result.getSecond() + " network file=" + netFile);
|
|
|
|
// Return the new network file
|
|
|
|
return netFile;
|
|
}
|
|
catch (org.alfresco.repo.security.permissions.AccessDeniedException ex)
|
|
{
|
|
// Debug
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
|
|
logger.debug("Create file - access denied, " + params.getFullPath());
|
|
|
|
// Convert to a filesystem access denied status
|
|
|
|
throw new AccessDeniedException("Create file " + params.getFullPath());
|
|
}
|
|
catch (ContentIOException ex)
|
|
{
|
|
// Debug
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
|
|
logger.debug("Create file - content I/O error, " + params.getFullPath());
|
|
|
|
// Convert to a filesystem disk full status
|
|
|
|
throw new DiskFullException("Create file " + params.getFullPath());
|
|
}
|
|
catch (AlfrescoRuntimeException ex)
|
|
{
|
|
// Debug
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
|
|
logger.debug("Create file error", ex);
|
|
|
|
// Convert to a general I/O exception
|
|
|
|
throw new IOException("Create file " + params.getFullPath());
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Create a new directory on this file system.
|
|
*
|
|
* @param sess Server session
|
|
* @param tree Tree connection.
|
|
* @param params Directory create parameters
|
|
* @exception java.io.IOException If an error occurs.
|
|
*/
|
|
public void createDirectory(SrvSession sess, final TreeConnection tree, final FileOpenParams params) throws IOException
|
|
{
|
|
final ContentContext ctx = (ContentContext) tree.getContext();
|
|
|
|
try
|
|
{
|
|
// Access the repository in a retryable write transaction
|
|
Pair<String, NodeRef> result = doInWriteTransaction(sess, new CallableIO<Pair<String, NodeRef>>()
|
|
{
|
|
|
|
public Pair<String, NodeRef> call() throws IOException
|
|
{
|
|
// get the device root
|
|
|
|
NodeRef deviceRootNodeRef = ctx.getRootNode();
|
|
|
|
String path = params.getPath();
|
|
String parentPath = null;
|
|
|
|
// If the state table is available then try to find the parent folder node for the new folder
|
|
// to save having to walk the path
|
|
|
|
if ( ctx.hasStateCache())
|
|
{
|
|
// See if the parent folder has a file state, we can avoid having to walk the path
|
|
|
|
String[] paths = FileName.splitPath(path);
|
|
if (paths[0] != null && paths[0].length() > 1)
|
|
{
|
|
// Find the node ref for the folder being searched
|
|
|
|
NodeRef nodeRef = getNodeForPath(tree, paths[0]);
|
|
|
|
if (nodeRef != null)
|
|
{
|
|
deviceRootNodeRef = nodeRef;
|
|
path = paths[1];
|
|
|
|
// DEBUG
|
|
|
|
if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
|
|
logger.debug("Create file using cached noderef for path " + paths[0]);
|
|
}
|
|
|
|
parentPath = paths[0];
|
|
}
|
|
}
|
|
|
|
// Create it - the path will be created, if necessary
|
|
|
|
NodeRef nodeRef = cifsHelper.createNode(deviceRootNodeRef, path, ContentModel.TYPE_FOLDER);
|
|
|
|
return new Pair<String, NodeRef>(parentPath, nodeRef);
|
|
}
|
|
});
|
|
|
|
// Get or create the file state for the parent folder
|
|
FileState parentState = null;
|
|
String parentPath = result.getFirst();
|
|
if (parentPath != null)
|
|
{
|
|
parentState = getStateForPath(tree, parentPath);
|
|
if (parentState == null && ctx.hasStateCache())
|
|
parentState = ctx.getStateCache().findFileState(parentPath, true);
|
|
}
|
|
|
|
// Add a file state for the new folder
|
|
|
|
if ( ctx.hasStateCache())
|
|
{
|
|
FileState fstate = ctx.getStateCache().findFileState( params.getPath(), true);
|
|
if ( fstate != null)
|
|
{
|
|
// Indicate that the file is open
|
|
|
|
fstate.setFileStatus( DirectoryExists);
|
|
fstate.setFilesystemObject(result.getSecond());
|
|
|
|
// DEBUG
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
|
|
logger.debug("Create folder, state=" + fstate);
|
|
}
|
|
|
|
// Update the parent folder file state
|
|
|
|
if ( parentState != null)
|
|
parentState.updateModifyDateTime();
|
|
}
|
|
|
|
// Debug
|
|
|
|
if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
|
|
logger.debug("Created directory: path=" + params.getPath() + " file open params=" + params + " node=" + result.getSecond());
|
|
}
|
|
catch (org.alfresco.repo.security.permissions.AccessDeniedException ex)
|
|
{
|
|
// Debug
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
|
|
logger.debug("Create directory - access denied, " + params.getFullPath());
|
|
|
|
// Convert to a filesystem access denied status
|
|
|
|
throw new AccessDeniedException("Create directory " + params.getFullPath());
|
|
}
|
|
catch (AlfrescoRuntimeException ex)
|
|
{
|
|
// Debug
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
|
|
logger.debug("Create directory error", ex);
|
|
|
|
// Convert to a general I/O exception
|
|
|
|
throw new IOException("Create directory " + params.getFullPath());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Delete the directory from the filesystem.
|
|
*
|
|
* @param sess Server session
|
|
* @param tree Tree connection
|
|
* @param dir Directory name.
|
|
* @exception java.io.IOException The exception description.
|
|
*/
|
|
public void deleteDirectory(SrvSession sess, TreeConnection tree, final String dir) throws IOException
|
|
{
|
|
// get the device root
|
|
|
|
ContentContext ctx = (ContentContext) tree.getContext();
|
|
final NodeRef deviceRootNodeRef = ctx.getRootNode();
|
|
|
|
try
|
|
{
|
|
NodeRef nodeRef = doInWriteTransaction(sess, new CallableIO<NodeRef>(){
|
|
|
|
public NodeRef call() throws IOException
|
|
{
|
|
// Get the node for the folder
|
|
|
|
NodeRef nodeRef = cifsHelper.getNodeRef(deviceRootNodeRef, dir);
|
|
if (fileFolderService.exists(nodeRef))
|
|
{
|
|
// Check if the folder is empty
|
|
|
|
if ( cifsHelper.isFolderEmpty( nodeRef))
|
|
{
|
|
// Delete the folder node
|
|
|
|
fileFolderService.delete(nodeRef);
|
|
return nodeRef;
|
|
}
|
|
else
|
|
{
|
|
throw new DirectoryNotEmptyException( dir);
|
|
}
|
|
}
|
|
return null;
|
|
}});
|
|
if (nodeRef != null && ctx.hasStateCache())
|
|
{
|
|
// Remove the file state
|
|
|
|
ctx.getStateCache().removeFileState(dir);
|
|
|
|
// Update, or create, a parent folder file state
|
|
|
|
String[] paths = FileName.splitPath(dir);
|
|
if ( paths[0] != null && paths[0].length() > 1)
|
|
{
|
|
// Get the file state for the parent folder
|
|
|
|
FileState parentState = getStateForPath(tree, paths[0]);
|
|
if ( parentState == null && ctx.hasStateCache())
|
|
parentState = ctx.getStateCache().findFileState( paths[0], true);
|
|
|
|
// Update the modification timestamp
|
|
|
|
parentState.updateModifyDateTime();
|
|
}
|
|
}
|
|
|
|
// Debug
|
|
|
|
if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
|
|
logger.debug("Deleted directory: directory=" + dir + " node=" + nodeRef);
|
|
}
|
|
catch (FileNotFoundException e)
|
|
{
|
|
// Debug
|
|
|
|
if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
|
|
logger.debug("Delete directory - file not found, " + dir);
|
|
}
|
|
catch (org.alfresco.repo.security.permissions.AccessDeniedException ex)
|
|
{
|
|
// Debug
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
|
|
logger.debug("Delete directory - access denied, " + dir);
|
|
|
|
// Convert to a filesystem access denied status
|
|
|
|
throw new AccessDeniedException("Delete directory " + dir);
|
|
}
|
|
catch (AlfrescoRuntimeException ex)
|
|
{
|
|
// Debug
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
|
|
logger.debug("Delete directory", ex);
|
|
|
|
// Convert to a general I/O exception
|
|
|
|
throw new IOException("Delete directory " + dir);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Flush any buffered output for the specified file.
|
|
*
|
|
* @param sess Server session
|
|
* @param tree Tree connection
|
|
* @param file Network file context.
|
|
* @exception java.io.IOException The exception description.
|
|
*/
|
|
public void flushFile(SrvSession sess, TreeConnection tree, NetworkFile file) throws IOException
|
|
{
|
|
// Debug
|
|
|
|
ContentContext ctx = (ContentContext) tree.getContext();
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILEIO))
|
|
logger.debug("Flush file=" + file.getFullName());
|
|
|
|
// Flush the file data
|
|
|
|
file.flushFile();
|
|
}
|
|
|
|
/**
|
|
* Close the file.
|
|
*
|
|
* @param sess Server session
|
|
* @param tree Tree connection.
|
|
* @param param Network file context.
|
|
* @exception java.io.IOException If an error occurs.
|
|
*/
|
|
public void closeFile(SrvSession sess, TreeConnection tree, final NetworkFile file) throws IOException
|
|
{
|
|
// Get the associated file state
|
|
|
|
final ContentContext ctx = (ContentContext) tree.getContext();
|
|
FileState toUpdate = null;
|
|
|
|
// Check for a content file
|
|
|
|
if ( file instanceof ContentNetworkFile) {
|
|
|
|
// Update the file state
|
|
|
|
if ( ctx.hasStateCache())
|
|
{
|
|
FileState fstate = ctx.getStateCache().findFileState(file.getFullName());
|
|
if ( fstate != null) {
|
|
|
|
// If the file open count is now zero then reset the stored sharing mode
|
|
|
|
if ( fstate.decrementOpenCount() == 0)
|
|
fstate.setSharedAccess( SharingMode.READWRITE + SharingMode.DELETE);
|
|
|
|
// Check if there is a cached modification timestamp to be written out
|
|
|
|
if ( file.hasDeleteOnClose() == false && fstate.hasModifyDateTime() && fstate.hasFilesystemObject() && fstate.isDirectory() == false) {
|
|
|
|
// Update the modification date on the file/folder node
|
|
toUpdate = fstate;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Decrement the file open count
|
|
|
|
ContentNetworkFile contentFile = (ContentNetworkFile) file;
|
|
|
|
if ( contentFile.decrementOpenCount() > 0) {
|
|
|
|
// DEBUG
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
|
|
logger.debug("Deferred file close, path=" + file.getFullName() + ", openCount=" + contentFile.getOpenCount());
|
|
|
|
// Defer the file close to the last reference
|
|
|
|
return;
|
|
}
|
|
else if ( logger.isDebugEnabled())
|
|
logger.debug("Last reference to file, closing, path=" + file.getFullName() + ", access=" + file.getGrantedAccessAsString() + ", fid=" + file.getProtocolId() +
|
|
", modified=" + contentFile.isModified());
|
|
}
|
|
|
|
// Check if there is a quota manager enabled
|
|
|
|
long fileSize = 0L;
|
|
|
|
if ( ctx.hasQuotaManager() && file.hasDeleteOnClose()) {
|
|
|
|
// Make sure the content stream has been opened, to get the current file size
|
|
|
|
if ( file instanceof ContentNetworkFile) {
|
|
ContentNetworkFile contentFile = (ContentNetworkFile) file;
|
|
if ( contentFile.hasContent() == false)
|
|
contentFile.openContent( false, false);
|
|
|
|
// Save the current file size
|
|
|
|
fileSize = contentFile.getFileSize();
|
|
}
|
|
}
|
|
|
|
// Perform repository updates in a retryable write transaction
|
|
final FileState finalFileState = toUpdate;
|
|
Pair<NodeRef, Boolean> result = doInWriteTransaction(sess, new CallableIO<Pair<NodeRef, Boolean>>()
|
|
{
|
|
public Pair<NodeRef, Boolean> call() throws IOException
|
|
{
|
|
// Check if the file is an OpenOffice document and hte truncation flag is set
|
|
//
|
|
// Note: Check before the timestamp update
|
|
|
|
if ( file instanceof OpenOfficeContentNetworkFile) {
|
|
OpenOfficeContentNetworkFile ooFile = (OpenOfficeContentNetworkFile) file;
|
|
if ( ooFile.truncatedToZeroLength()) {
|
|
|
|
// Inhibit versioning for this transaction
|
|
|
|
getPolicyFilter().disableBehaviour( ContentModel.ASPECT_VERSIONABLE);
|
|
|
|
// Debug
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
|
|
logger.debug("OpenOffice file truncation update only, inhibit versioning, " + file.getFullName());
|
|
}
|
|
}
|
|
|
|
// Update the modification date on the file/folder node
|
|
if (finalFileState != null && file instanceof ContentNetworkFile)
|
|
{
|
|
NodeRef nodeRef = (NodeRef) finalFileState.getFilesystemObject();
|
|
|
|
// Check if the file data has been updated, if not then inhibit versioning for this txn
|
|
// so the timestamp update does not generate a new file version
|
|
|
|
ContentNetworkFile contentFile = (ContentNetworkFile) file;
|
|
if ( contentFile.isModified() == false && nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE)) {
|
|
|
|
// Stop a new file version being generated
|
|
|
|
getPolicyFilter().disableBehaviour( ContentModel.ASPECT_VERSIONABLE);
|
|
|
|
// Debug
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
|
|
logger.debug("Timestamp update only, inhibit versioning, " + file.getFullName());
|
|
}
|
|
|
|
// Update the modification timestamp
|
|
|
|
getPolicyFilter().disableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE);
|
|
|
|
if (permissionService.hasPermission((NodeRef) finalFileState.getFilesystemObject(), PermissionService.WRITE_PROPERTIES) == AccessStatus.ALLOWED)
|
|
{
|
|
Date modifyDate = new Date(finalFileState.getModifyDateTime());
|
|
nodeService.setProperty(nodeRef, ContentModel.PROP_MODIFIED, modifyDate);
|
|
|
|
// Debug
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
|
|
logger.debug("Updated modification timestamp, " + file.getFullName() + ", modTime=" + modifyDate);
|
|
}
|
|
}
|
|
|
|
// Defer to the network file to close the stream and remove the content
|
|
|
|
file.closeFile();
|
|
|
|
// Remove the node if marked for delete
|
|
|
|
if (file.hasDeleteOnClose())
|
|
{
|
|
// Check if the file is a noderef based file
|
|
|
|
if ( file instanceof NodeRefNetworkFile)
|
|
{
|
|
NodeRefNetworkFile nodeNetFile = (NodeRefNetworkFile) file;
|
|
NodeRef nodeRef = nodeNetFile.getNodeRef();
|
|
|
|
// We don't know how long the network file has had the reference, so check for existence
|
|
|
|
if (fileFolderService.exists(nodeRef))
|
|
{
|
|
try
|
|
{
|
|
boolean isVersionable = nodeService.hasAspect( nodeRef, ContentModel.ASPECT_VERSIONABLE);
|
|
|
|
try
|
|
{
|
|
// Delete the file
|
|
|
|
fileFolderService.delete(nodeRef);
|
|
|
|
}
|
|
catch ( Exception ex)
|
|
{
|
|
// Propagate retryable errors. Log the rest.
|
|
if (RetryingTransactionHelper.extractRetryCause(ex) != null)
|
|
{
|
|
if (ex instanceof RuntimeException)
|
|
{
|
|
throw (RuntimeException)ex;
|
|
}
|
|
else
|
|
{
|
|
throw new AlfrescoRuntimeException("Error during delete on close, " + file.getFullName(), ex);
|
|
}
|
|
}
|
|
if ( logger.isWarnEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
|
|
logger.warn("Error during delete on close, " + file.getFullName(), ex);
|
|
}
|
|
|
|
// Return a node ref to update in the state table
|
|
return new Pair<NodeRef, Boolean>(nodeRef, isVersionable);
|
|
}
|
|
catch (org.alfresco.repo.security.permissions.AccessDeniedException ex)
|
|
{
|
|
// Debug
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
|
|
logger.debug("Delete on close - access denied, " + file.getFullName());
|
|
|
|
// Convert to a filesystem access denied exception
|
|
|
|
throw new AccessDeniedException("Delete on close " + file.getFullName());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}});
|
|
|
|
if (result != null)
|
|
{
|
|
// Check if there is a quota manager enabled, release space back to the user quota
|
|
|
|
if ( ctx.hasQuotaManager())
|
|
ctx.getQuotaManager().releaseSpace(sess, tree, file.getFileId(), file.getFullName(), fileSize);
|
|
|
|
// Set the file state to indicate a delete on close
|
|
|
|
if (ctx.hasStateCache())
|
|
{
|
|
if (result.getSecond())
|
|
{
|
|
|
|
// Get, or create, the file state
|
|
|
|
FileState fState = ctx.getStateCache().findFileState(file.getFullName(), true);
|
|
|
|
// Indicate that the file was deleted via a delete on close request
|
|
|
|
fState.setFileStatus(DeleteOnClose);
|
|
|
|
// Make sure the file state is cached for a short while, save the noderef details
|
|
|
|
fState.setExpiryTime(System.currentTimeMillis() + FileState.RenameTimeout);
|
|
fState.setFilesystemObject(result.getFirst());
|
|
}
|
|
else
|
|
{
|
|
|
|
// Remove the file state
|
|
|
|
ctx.getStateCache().removeFileState(file.getFullName());
|
|
}
|
|
}
|
|
}
|
|
else if (file.hasDeleteOnClose() && (file instanceof PseudoNetworkFile || file instanceof MemoryNetworkFile)
|
|
&& hasPseudoFileInterface(ctx))
|
|
{
|
|
// Delete the pseudo file
|
|
|
|
getPseudoFileInterface(ctx).deletePseudoFile(sess, tree, file.getFullName());
|
|
|
|
}
|
|
|
|
// DEBUG
|
|
|
|
if (logger.isDebugEnabled() && (ctx.hasDebug(AlfrescoContext.DBG_FILE) || ctx.hasDebug(AlfrescoContext.DBG_RENAME))) {
|
|
logger.debug("Closed file: network file=" + file + " delete on close=" + file.hasDeleteOnClose());
|
|
if ( file.hasDeleteOnClose() == false && file instanceof ContentNetworkFile) {
|
|
ContentNetworkFile cFile = (ContentNetworkFile) file;
|
|
logger.debug(" File " + file.getFullName() + ", version=" + nodeService.getProperty( cFile.getNodeRef(), ContentModel.PROP_VERSION_LABEL));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Delete the specified file.
|
|
*
|
|
* @param sess Server session
|
|
* @param tree Tree connection
|
|
* @param file NetworkFile
|
|
* @exception java.io.IOException The exception description.
|
|
*/
|
|
public void deleteFile(final SrvSession sess, final TreeConnection tree, final String name) throws IOException
|
|
{
|
|
// Get the device context
|
|
|
|
final ContentContext ctx = (ContentContext) tree.getContext();
|
|
|
|
try
|
|
{
|
|
// Check if pseudo files are enabled
|
|
|
|
if ( hasPseudoFileInterface(ctx))
|
|
{
|
|
// Check if the file name is a pseudo file name
|
|
|
|
if ( getPseudoFileInterface( ctx).isPseudoFile(sess, tree, name)) {
|
|
|
|
// Make sure the parent folder has a file state, and the path exists
|
|
|
|
String[] paths = FileName.splitPath( name);
|
|
FileState fstate = ctx.getStateCache().findFileState( paths[0]);
|
|
|
|
if ( fstate != null) {
|
|
|
|
// Check if the path is to a pseudo file
|
|
|
|
PseudoFile pfile = getPseudoFileInterface(ctx).getPseudoFile( sess, tree, name);
|
|
if ( pfile != null)
|
|
{
|
|
// Delete the pseudo file
|
|
|
|
getPseudoFileInterface( ctx).deletePseudoFile( sess, tree, name);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check if there is a quota manager enabled, if so then we need to save the current file size
|
|
|
|
final QuotaManager quotaMgr = ctx.getQuotaManager();
|
|
|
|
// Perform repository updates in a retryable write transaction
|
|
Callable<Void> postTxn = doInWriteTransaction(sess, new CallableIO<Callable<Void>>()
|
|
{
|
|
public Callable<Void> call() throws IOException
|
|
{
|
|
// Get the node and delete it
|
|
final NodeRef nodeRef = getNodeForPath(tree, name);
|
|
|
|
Callable<Void> result = null;
|
|
if (fileFolderService.exists(nodeRef))
|
|
{
|
|
// Get the size of the file being deleted
|
|
|
|
final FileInfo fInfo = quotaMgr == null ? null : getFileInformation(sess, tree, name);
|
|
|
|
// Check if the node is versionable
|
|
|
|
final boolean isVersionable = nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE);
|
|
|
|
fileFolderService.delete(nodeRef);
|
|
|
|
// Return the operations to perform when the transaction succeeds
|
|
|
|
result = new Callable<Void>()
|
|
{
|
|
|
|
public Void call() throws Exception
|
|
{
|
|
// Remove the file state
|
|
|
|
if (ctx.hasStateCache())
|
|
{
|
|
// Check if the node is versionable, cache the node details for a short while
|
|
|
|
if (isVersionable == true)
|
|
{
|
|
|
|
// Make sure the file state is cached for a short while, a new file may be
|
|
// renamed to the same name
|
|
// in which case we can connect the file to the previous version history
|
|
|
|
FileState delState = ctx.getStateCache().findFileState(name, true);
|
|
|
|
delState.setExpiryTime(System.currentTimeMillis() + FileState.DeleteTimeout);
|
|
delState.setFileStatus(DeleteOnClose);
|
|
delState.setFilesystemObject(nodeRef);
|
|
}
|
|
else
|
|
{
|
|
|
|
// Remove the file state
|
|
|
|
ctx.getStateCache().removeFileState(name);
|
|
}
|
|
|
|
// Update, or create, a parent folder file state
|
|
|
|
String[] paths = FileName.splitPath(name);
|
|
if (paths[0] != null && paths[0].length() > 1)
|
|
{
|
|
// Get the file state for the parent folder
|
|
|
|
FileState parentState = getStateForPath(tree, paths[0]);
|
|
if (parentState == null && ctx.hasStateCache())
|
|
parentState = ctx.getStateCache().findFileState(paths[0], true);
|
|
|
|
// Update the modification timestamp
|
|
|
|
parentState.updateModifyDateTime();
|
|
}
|
|
}
|
|
|
|
// Release the space back to the users quota
|
|
|
|
if (quotaMgr != null)
|
|
quotaMgr.releaseSpace(sess, tree, fInfo.getFileId(), name, fInfo.getSize());
|
|
|
|
return null;
|
|
}
|
|
};
|
|
}
|
|
|
|
// Debug
|
|
|
|
if (logger.isDebugEnabled() && (ctx.hasDebug(AlfrescoContext.DBG_FILE) || ctx.hasDebug(AlfrescoContext.DBG_RENAME)))
|
|
logger.debug("Deleted file: " + name + ", node=" + nodeRef);
|
|
|
|
return result;
|
|
}
|
|
});
|
|
|
|
// Perform state updates after the transaction succeeds
|
|
postTxn.call();
|
|
}
|
|
catch (NodeLockedException ex)
|
|
{
|
|
// Debug
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
|
|
logger.debug("Delete file - access denied (locked)");
|
|
|
|
// Convert to a filesystem access denied status
|
|
|
|
throw new AccessDeniedException("Delete " + name);
|
|
}
|
|
catch (org.alfresco.repo.security.permissions.AccessDeniedException ex)
|
|
{
|
|
// Debug
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
|
|
logger.debug("Delete file - access denied");
|
|
|
|
// Convert to a filesystem access denied status
|
|
|
|
throw new AccessDeniedException("Delete " + name);
|
|
}
|
|
catch (IOException ex)
|
|
{
|
|
// Allow I/O Exceptions to pass through
|
|
throw ex;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// Debug
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
|
|
logger.debug("Delete file error", ex);
|
|
|
|
// Convert to a general I/O exception
|
|
|
|
IOException ioe = new IOException("Delete file " + name);
|
|
ioe.initCause(ex);
|
|
throw ioe;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Rename the specified file.
|
|
*
|
|
* @param sess Server session
|
|
* @param tree Tree connection
|
|
* @param oldName java.lang.String
|
|
* @param newName java.lang.String
|
|
* @exception java.io.IOException The exception description.
|
|
*/
|
|
public void renameFile(final SrvSession sess, final TreeConnection tree, final String oldName, final String newName)
|
|
throws IOException
|
|
{
|
|
// Create the transaction (initially read-only)
|
|
|
|
beginReadTransaction(sess);
|
|
|
|
// Get the device context
|
|
|
|
final ContentContext ctx = (ContentContext) tree.getContext();
|
|
|
|
// DEBUG
|
|
|
|
if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
|
|
logger.debug("Rename oldName=" + oldName + ", newName=" + newName);
|
|
|
|
try
|
|
{
|
|
// Get the file/folder to move
|
|
|
|
final NodeRef nodeToMoveRef = getNodeForPath(tree, oldName);
|
|
|
|
// Check if the node is a link node
|
|
|
|
if ( nodeToMoveRef != null && nodeService.getProperty(nodeToMoveRef, ContentModel.PROP_LINK_DESTINATION) != null)
|
|
throw new AccessDeniedException("Cannot rename link nodes");
|
|
|
|
// Get the new target folder - it must be a folder
|
|
|
|
String[] splitPaths = FileName.splitPath(newName);
|
|
String[] oldPaths = FileName.splitPath(oldName);
|
|
|
|
final NodeRef targetFolderRef = getNodeForPath(tree, splitPaths[0]);
|
|
final NodeRef sourceFolderRef = getNodeForPath(tree, oldPaths[0]);
|
|
final String name = splitPaths[1];
|
|
|
|
// Check if this is a rename within the same folder
|
|
|
|
final boolean sameFolder = splitPaths[0].equalsIgnoreCase(oldPaths[0]);
|
|
|
|
// Get the file state for the old file, if available
|
|
|
|
final FileState oldState = ctx.getStateCache().findFileState(oldName, true);
|
|
|
|
// Check if we are renaming a folder, or the rename is to a different folder
|
|
|
|
boolean isFolder = cifsHelper.isDirectory(nodeToMoveRef);
|
|
|
|
if ( isFolder == true || sameFolder == false) {
|
|
|
|
// Rename or move the file/folder
|
|
|
|
doInWriteTransaction(sess, new CallableIO<Void>()
|
|
{
|
|
|
|
public Void call() throws IOException
|
|
{
|
|
if (sameFolder == true)
|
|
cifsHelper.rename(nodeToMoveRef, name);
|
|
else
|
|
cifsHelper.move(nodeToMoveRef, sourceFolderRef, targetFolderRef, name);
|
|
return null;
|
|
}
|
|
});
|
|
|
|
// Update the old file state
|
|
|
|
if (oldState != null)
|
|
{
|
|
// Update the file state index to use the new name
|
|
|
|
ctx.getStateCache().renameFileState(newName, oldState, true);
|
|
}
|
|
|
|
// DEBUG
|
|
|
|
if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
|
|
logger.debug(" Renamed " + (isFolder ? "folder" : "file") + " using "
|
|
+ (sameFolder ? "rename" : "move"));
|
|
}
|
|
else {
|
|
|
|
// Rename a file within the same folder
|
|
//
|
|
// Check if the target file already exists
|
|
|
|
final int newExists = fileExists(sess, tree, newName);
|
|
final FileState newState = ctx.getStateCache().findFileState(newName, true);
|
|
|
|
List<Runnable> postTxn = doInWriteTransaction(sess, new CallableIO<List<Runnable>>()
|
|
{
|
|
|
|
public List<Runnable> call() throws IOException
|
|
{
|
|
List<Runnable> postTxn = new LinkedList<Runnable>();
|
|
|
|
NodeRef targetNodeRef = null;
|
|
|
|
boolean isFromVersionable = nodeService.hasAspect( nodeToMoveRef, ContentModel.ASPECT_VERSIONABLE);
|
|
boolean typesCompatible = true;
|
|
|
|
if ( newExists == FileStatus.FileExists) {
|
|
|
|
// Use the existing file as the target node
|
|
|
|
targetNodeRef = getNodeForPath( tree, newName);
|
|
}
|
|
else {
|
|
|
|
// Check if the target has a renamed or delete-on-close state
|
|
|
|
if ( newState.getFileStatus() == FileRenamed) {
|
|
|
|
// DEBUG
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
|
|
logger.debug(" Using renamed node, " + newState);
|
|
|
|
NodeRef newStateNode = (NodeRef)newState.getFilesystemObject();
|
|
QName oldType = nodeService.getType(nodeToMoveRef);
|
|
QName newType = nodeService.getType(newStateNode);
|
|
if (oldType.equals(newType)) {
|
|
|
|
// Use the renamed node to clone aspects/state if it is of the correct type
|
|
|
|
cloneNode(name, newStateNode, nodeToMoveRef, ctx);
|
|
}
|
|
else
|
|
{
|
|
// Otherwise we must create a node of the correct type
|
|
targetNodeRef = cifsHelper.createNode(ctx.getRootNode(), newName, newType);
|
|
|
|
// Force a copy to this target
|
|
typesCompatible = false;
|
|
|
|
// DEBUG
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
|
|
logger.debug(" Created new node for " + newName + " type " + newType);
|
|
|
|
// Copy aspects from the original state
|
|
|
|
cloneNode( name, newStateNode, targetNodeRef, ctx);
|
|
}
|
|
}
|
|
else if ( newState.getFileStatus() == DeleteOnClose) {
|
|
|
|
// DEBUG
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
|
|
logger.debug(" Restoring delete-on-close node, " + newState);
|
|
|
|
// Restore the deleted node so we can relink the new version to the old history/properties
|
|
|
|
NodeRef archivedNode = getNodeArchiveService().getArchivedNode((NodeRef) newState.getFilesystemObject());
|
|
|
|
// DEBUG
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
|
|
logger.debug(" Found archived node " + archivedNode);
|
|
|
|
if ( archivedNode != null )
|
|
{
|
|
// Restore the node
|
|
|
|
targetNodeRef = getNodeService().restoreNode( archivedNode, null, null, null);
|
|
|
|
// DEBUG
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
|
|
logger.debug(" Restored node " + targetNodeRef + ", version=" + nodeService.getProperty( targetNodeRef, ContentModel.PROP_VERSION_LABEL));
|
|
|
|
// Check if the deleted file had a linked node, due to a rename
|
|
|
|
NodeRef linkNode = (NodeRef) newState.findAttribute( AttrLinkNode);
|
|
|
|
if ( linkNode != null && nodeService.exists( linkNode)) {
|
|
|
|
// Clone aspects from the linked node onto the restored node
|
|
|
|
cloneNode( name, linkNode, targetNodeRef, ctx);
|
|
|
|
// DEBUG
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME)) {
|
|
logger.debug(" Moved aspects from linked node " + linkNode);
|
|
|
|
// Check if the node is a working copy
|
|
|
|
if ( nodeService.hasAspect( targetNodeRef, ContentModel.ASPECT_WORKING_COPY)) {
|
|
|
|
// Check if the main document is still locked
|
|
|
|
NodeRef mainNodeRef = (NodeRef) nodeService.getProperty( targetNodeRef, ContentModel.PROP_COPY_REFERENCE);
|
|
if ( mainNodeRef != null) {
|
|
LockType lockTyp = lockService.getLockType( mainNodeRef);
|
|
logger.debug(" Main node ref lock type = " + lockTyp);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check if the node being renamed is versionable
|
|
|
|
else if ( isFromVersionable == true) {
|
|
|
|
// Create a new node for the target
|
|
|
|
targetNodeRef = cifsHelper.createNode(ctx.getRootNode(), newName, nodeService.getType(nodeToMoveRef));
|
|
|
|
// DEBUG
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
|
|
logger.debug(" Created new node for " + newName);
|
|
|
|
// Copy aspects from the original file
|
|
|
|
cloneNode( name, nodeToMoveRef, targetNodeRef, ctx);
|
|
}
|
|
}
|
|
|
|
// If the original or target nodes are not versionable and types are compatible then just use a standard rename of the
|
|
// node
|
|
if ( isFromVersionable == false && typesCompatible &&
|
|
( targetNodeRef == null || nodeService.hasAspect( targetNodeRef, ContentModel.ASPECT_VERSIONABLE) == false)) {
|
|
|
|
// Rename the file/folder
|
|
|
|
cifsHelper.rename(nodeToMoveRef, name);
|
|
|
|
postTxn.add(new Runnable()
|
|
{
|
|
public void run()
|
|
{
|
|
// Mark the new file as existing
|
|
|
|
newState.setFileStatus(FileExists);
|
|
newState.setFilesystemObject(nodeToMoveRef);
|
|
|
|
// Make sure the old file state is cached for a short while, the file may not be open so the
|
|
// file state could be expired
|
|
|
|
oldState.setExpiryTime(System.currentTimeMillis() + FileState.DeleteTimeout);
|
|
|
|
// Indicate that this is a renamed file state, set the node ref of the file that was renamed
|
|
|
|
oldState.setFileStatus(FileRenamed);
|
|
oldState.setFilesystemObject(nodeToMoveRef);
|
|
}
|
|
});
|
|
|
|
// DEBUG
|
|
|
|
if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
|
|
logger.debug(" Use standard rename for " + name + "(versionable=" + isFromVersionable + ", targetNodeRef=" + targetNodeRef + ")");
|
|
}
|
|
else {
|
|
|
|
// Make sure we have a valid target node
|
|
|
|
if ( targetNodeRef == null) {
|
|
|
|
// DEBUG
|
|
|
|
if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
|
|
logger.debug(" No target node for rename");
|
|
|
|
// Throw an error
|
|
|
|
throw new AccessDeniedException("No target node for file rename");
|
|
}
|
|
|
|
// Copy content data from the old file to the new file
|
|
|
|
copyContentData(sess, tree, nodeToMoveRef, targetNodeRef, newName);
|
|
|
|
final NodeRef finalTargetNodeRef = targetNodeRef;
|
|
postTxn.add(new Runnable()
|
|
{
|
|
|
|
public void run()
|
|
{
|
|
// Mark the new file as existing
|
|
|
|
newState.setFileStatus(FileExists);
|
|
newState.setFilesystemObject(finalTargetNodeRef);
|
|
|
|
// Make sure the old file state is cached for a short while, the file may not be open so the
|
|
// file state could be expired
|
|
|
|
oldState.setExpiryTime(System.currentTimeMillis() + FileState.DeleteTimeout);
|
|
|
|
// Indicate that this is a deleted file state, set the node ref of the file that was renamed
|
|
|
|
oldState.setFileStatus( DeleteOnClose);
|
|
oldState.setFilesystemObject(nodeToMoveRef);
|
|
|
|
// Link to the new node, a new file may be renamed into place, we need to transfer aspect/locks
|
|
|
|
oldState.addAttribute( AttrLinkNode, finalTargetNodeRef);
|
|
|
|
// DEBUG
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug( AlfrescoContext.DBG_RENAME))
|
|
logger.debug(" Cached delete state for " + oldName);
|
|
}
|
|
});
|
|
|
|
// Delete the old file
|
|
|
|
nodeService.deleteNode(nodeToMoveRef);
|
|
|
|
}
|
|
|
|
return postTxn;
|
|
}
|
|
});
|
|
|
|
// Run the required state-changing logic once the retrying transaction has completed successfully
|
|
for (Runnable runnable : postTxn)
|
|
{
|
|
runnable.run();
|
|
}
|
|
}
|
|
}
|
|
catch (org.alfresco.repo.security.permissions.AccessDeniedException ex)
|
|
{
|
|
// Debug
|
|
|
|
if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
|
|
logger.debug("Rename file - access denied, " + oldName);
|
|
|
|
// Convert to a filesystem access denied status
|
|
|
|
throw new AccessDeniedException("Rename file " + oldName);
|
|
}
|
|
catch (NodeLockedException ex)
|
|
{
|
|
// Debug
|
|
|
|
if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
|
|
logger.debug("Rename file", ex);
|
|
|
|
// Convert to an filesystem access denied exception
|
|
|
|
throw new AccessDeniedException("Node locked " + oldName);
|
|
}
|
|
catch (AlfrescoRuntimeException ex)
|
|
{
|
|
// Debug
|
|
|
|
if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
|
|
logger.debug("Rename file", ex);
|
|
|
|
// Convert to a general I/O exception
|
|
|
|
throw new AccessDeniedException("Rename file " + oldName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set file information
|
|
*
|
|
* @param sess SrvSession
|
|
* @param tree TreeConnection
|
|
* @param name String
|
|
* @param info FileInfo
|
|
* @exception IOException
|
|
*/
|
|
public void setFileInformation(SrvSession sess, final TreeConnection tree, final String name, final FileInfo info) throws IOException
|
|
{
|
|
// Get the device context
|
|
|
|
final ContentContext ctx = (ContentContext) tree.getContext();
|
|
|
|
try
|
|
{
|
|
// Check if pseudo files are enabled
|
|
|
|
if ( hasPseudoFileInterface(ctx) &&
|
|
getPseudoFileInterface(ctx).isPseudoFile( sess, tree, name))
|
|
{
|
|
// Allow the file information to be changed
|
|
|
|
return;
|
|
}
|
|
|
|
final FileState fstate = getStateForPath(tree, name);
|
|
|
|
doInWriteTransaction(sess, new CallableIO<Pair<Boolean, Boolean>>(){
|
|
|
|
public Pair<Boolean, Boolean> call() throws IOException
|
|
{
|
|
// Get the file/folder node
|
|
|
|
NodeRef nodeRef = getNodeForPath(tree, name);
|
|
|
|
// Check permissions on the file/folder node
|
|
|
|
if ( permissionService.hasPermission(nodeRef, PermissionService.WRITE) == AccessStatus.DENIED)
|
|
throw new AccessDeniedException("No write access to " + name);
|
|
|
|
if ( permissionService.hasPermission(nodeRef, PermissionService.DELETE) == AccessStatus.DENIED)
|
|
throw new AccessDeniedException("No delete access to " + name);
|
|
|
|
// Inhibit versioning for this transaction
|
|
|
|
getPolicyFilter().disableBehaviour( ContentModel.ASPECT_VERSIONABLE);
|
|
|
|
// Check if the file is being marked for deletion, if so then check if the file is locked
|
|
|
|
if ( info.hasSetFlag(FileInfo.SetDeleteOnClose) && info.hasDeleteOnClose())
|
|
{
|
|
// Check if the node is locked
|
|
|
|
if ( nodeService.hasAspect( nodeRef, ContentModel.ASPECT_LOCKABLE))
|
|
{
|
|
// Get the lock type, if any
|
|
|
|
String lockTypeStr = (String) nodeService.getProperty( nodeRef, ContentModel.PROP_LOCK_TYPE);
|
|
|
|
if ( lockTypeStr != null)
|
|
throw new AccessDeniedException("Node locked, cannot mark for delete");
|
|
}
|
|
|
|
// Get the node for the folder
|
|
|
|
if (fileFolderService.exists(nodeRef))
|
|
{
|
|
// Check if it is a folder that is being deleted, make sure it is empty
|
|
|
|
boolean isFolder = true;
|
|
|
|
if ( fstate != null)
|
|
isFolder = fstate.isDirectory();
|
|
else {
|
|
ContentFileInfo cInfo = cifsHelper.getFileInformation( nodeRef);
|
|
if ( cInfo != null && cInfo.isDirectory() == false)
|
|
isFolder = false;
|
|
}
|
|
|
|
// Check if the folder is empty
|
|
|
|
if ( isFolder == true && cifsHelper.isFolderEmpty( nodeRef) == false)
|
|
throw new DirectoryNotEmptyException( name);
|
|
}
|
|
|
|
// DEBUG
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
|
|
logger.debug("Set deleteOnClose=true file=" + name);
|
|
}
|
|
|
|
// Set the creation and modified date/time
|
|
Map<QName, Serializable> auditableProps = new HashMap<QName, Serializable>(5);
|
|
|
|
if ( info.hasSetFlag(FileInfo.SetCreationDate))
|
|
{
|
|
// Set the creation date on the file/folder node
|
|
Date createDate = new Date( info.getCreationDateTime());
|
|
auditableProps.put(ContentModel.PROP_CREATED, createDate);
|
|
}
|
|
if ( info.hasSetFlag(FileInfo.SetModifyDate)) {
|
|
|
|
// Set the modification date on the file/folder node
|
|
Date modifyDate = new Date( info.getModifyDateTime());
|
|
auditableProps.put(ContentModel.PROP_MODIFIED, modifyDate);
|
|
}
|
|
|
|
// Did we have any cm:auditable properties?
|
|
if (auditableProps.size() > 0)
|
|
{
|
|
getPolicyFilter().disableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE);
|
|
nodeService.addProperties(nodeRef, auditableProps);
|
|
|
|
// DEBUG
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
|
|
logger.debug("Set auditable props: " + auditableProps + " file=" + name);
|
|
}
|
|
|
|
return null;
|
|
}});
|
|
// Check if the file is being marked for deletion, if so then check if the file is locked
|
|
|
|
// Update the change date/time
|
|
if (fstate != null)
|
|
{
|
|
if (info.hasSetFlag(FileInfo.SetDeleteOnClose) && info.hasDeleteOnClose()
|
|
|| info.hasSetFlag(FileInfo.SetCreationDate))
|
|
{
|
|
|
|
fstate.updateChangeDateTime();
|
|
}
|
|
|
|
// Set the modification date/time
|
|
|
|
if (info.hasSetFlag(FileInfo.SetModifyDate))
|
|
{
|
|
|
|
// Update the change date/time, clear the cached modification date/time
|
|
fstate.updateChangeDateTime();
|
|
fstate.updateModifyDateTime(0L);
|
|
}
|
|
}
|
|
}
|
|
catch (org.alfresco.repo.security.permissions.AccessDeniedException ex)
|
|
{
|
|
// Debug
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
|
|
logger.debug("Set file information - access denied, " + name);
|
|
|
|
// Convert to a filesystem access denied status
|
|
|
|
throw new AccessDeniedException("Set file information " + name);
|
|
}
|
|
catch (AlfrescoRuntimeException ex)
|
|
{
|
|
// Debug
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
|
|
logger.debug("Open file error", ex);
|
|
|
|
// Convert to a general I/O exception
|
|
|
|
throw new IOException("Set file information " + name);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Truncate a file to the specified size
|
|
*
|
|
* @param sess Server session
|
|
* @param tree Tree connection
|
|
* @param file Network file details
|
|
* @param size New file length
|
|
* @exception java.io.IOException The exception description.
|
|
*/
|
|
public void truncateFile(SrvSession sess, TreeConnection tree, NetworkFile file, long size) throws IOException
|
|
{
|
|
// Keep track of the allocation/release size in case the file resize fails
|
|
|
|
ContentContext ctx = (ContentContext) tree.getContext();
|
|
|
|
long allocSize = 0L;
|
|
long releaseSize = 0L;
|
|
|
|
// Check if there is a quota manager
|
|
|
|
QuotaManager quotaMgr = ctx.getQuotaManager();
|
|
|
|
if ( ctx.hasQuotaManager()) {
|
|
|
|
// Check if the file content has been opened, we need the content to be opened to get the
|
|
// current file size
|
|
|
|
if ( file instanceof ContentNetworkFile) {
|
|
ContentNetworkFile contentFile = (ContentNetworkFile) file;
|
|
if ( contentFile.hasContent() == false)
|
|
contentFile.openContent( false, false);
|
|
}
|
|
else
|
|
throw new IOException("Invalid file class type, " + file.getClass().getName());
|
|
|
|
// Determine if the new file size will release space or require space allocating
|
|
|
|
if ( size > file.getFileSize()) {
|
|
|
|
// Calculate the space to be allocated
|
|
|
|
allocSize = size - file.getFileSize();
|
|
|
|
// Allocate space to extend the file
|
|
|
|
quotaMgr.allocateSpace(sess, tree, file, allocSize);
|
|
}
|
|
else {
|
|
|
|
// Calculate the space to be released as the file is to be truncated, release the space if
|
|
// the file truncation is successful
|
|
|
|
releaseSize = file.getFileSize() - size;
|
|
}
|
|
}
|
|
|
|
// Check if this is a file extend, update the cached allocation size if necessary
|
|
|
|
if ( file instanceof ContentNetworkFile) {
|
|
|
|
// Get the cached state for the file
|
|
|
|
ContentNetworkFile contentFile = (ContentNetworkFile) file;
|
|
FileState fstate = contentFile.getFileState();
|
|
if ( fstate != null && size > fstate.getAllocationSize())
|
|
fstate.setAllocationSize( size);
|
|
}
|
|
|
|
// Set the file length
|
|
|
|
try {
|
|
file.truncateFile(size);
|
|
}
|
|
catch (IOException ex) {
|
|
|
|
// Check if we allocated space to the file
|
|
|
|
if ( allocSize > 0 && quotaMgr != null)
|
|
quotaMgr.releaseSpace(sess, tree, file.getFileId(), null, allocSize);
|
|
|
|
// Rethrow the exception
|
|
|
|
throw ex;
|
|
}
|
|
|
|
// Check if space has been released by the file resizing
|
|
|
|
if ( releaseSize > 0 && quotaMgr != null)
|
|
quotaMgr.releaseSpace(sess, tree, file.getFileId(), null, releaseSize);
|
|
|
|
// Debug
|
|
|
|
if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILEIO))
|
|
logger.debug("Truncated file: network file=" + file + " size=" + size);
|
|
}
|
|
|
|
/**
|
|
* Read a block of data from the specified file.
|
|
*
|
|
* @param sess Session details
|
|
* @param tree Tree connection
|
|
* @param file Network file
|
|
* @param buf Buffer to return data to
|
|
* @param bufPos Starting position in the return buffer
|
|
* @param siz Maximum size of data to return
|
|
* @param filePos File offset to read data
|
|
* @return Number of bytes read
|
|
* @exception java.io.IOException The exception description.
|
|
*/
|
|
public int readFile(
|
|
SrvSession sess, TreeConnection tree, NetworkFile file,
|
|
byte[] buffer, int bufferPosition, int size, long fileOffset) throws IOException
|
|
{
|
|
// Check if the file is a directory
|
|
|
|
if(file.isDirectory())
|
|
throw new AccessDeniedException();
|
|
|
|
// If the content channel is not open for the file then start a transaction
|
|
|
|
if ( file instanceof ContentNetworkFile)
|
|
{
|
|
ContentNetworkFile contentFile = (ContentNetworkFile) file;
|
|
|
|
if ( contentFile.hasContent() == false)
|
|
beginReadTransaction( sess);
|
|
}
|
|
|
|
// Read a block of data from the file
|
|
|
|
int count = file.readFile(buffer, size, bufferPosition, fileOffset);
|
|
|
|
if ( count == -1)
|
|
{
|
|
// Read count of -1 indicates a read past the end of file
|
|
|
|
count = 0;
|
|
}
|
|
|
|
// Debug
|
|
|
|
ContentContext ctx = (ContentContext) tree.getContext();
|
|
|
|
if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILEIO))
|
|
logger.debug("Read bytes from file: network file=" + file + " buffer size=" + buffer.length + " buffer pos=" + bufferPosition +
|
|
" size=" + size + " file offset=" + fileOffset + " bytes read=" + count);
|
|
|
|
return count;
|
|
}
|
|
|
|
/**
|
|
* Seek to the specified file position.
|
|
*
|
|
* @param sess Server session
|
|
* @param tree Tree connection
|
|
* @param file Network file.
|
|
* @param pos Position to seek to.
|
|
* @param typ Seek type.
|
|
* @return New file position, relative to the start of file.
|
|
*/
|
|
public long seekFile(SrvSession sess, TreeConnection tree, NetworkFile file, long pos, int typ) throws IOException
|
|
{
|
|
// Check if the file is a directory
|
|
|
|
if ( file.isDirectory())
|
|
throw new AccessDeniedException();
|
|
|
|
// If the content channel is not open for the file then start a transaction
|
|
|
|
ContentNetworkFile contentFile = (ContentNetworkFile) file;
|
|
|
|
if ( contentFile.hasContent() == false)
|
|
beginReadTransaction( sess);
|
|
|
|
// Set the file position
|
|
|
|
return file.seekFile(pos, typ);
|
|
}
|
|
|
|
/**
|
|
* Write a block of data to the file.
|
|
*
|
|
* @param sess Server session
|
|
* @param tree Tree connection
|
|
* @param file Network file details
|
|
* @param buf byte[] Data to be written
|
|
* @param bufoff Offset within the buffer that the data starts
|
|
* @param siz int Data length
|
|
* @param fileoff Position within the file that the data is to be written.
|
|
* @return Number of bytes actually written
|
|
* @exception java.io.IOException The exception description.
|
|
*/
|
|
public int writeFile(SrvSession sess, TreeConnection tree, NetworkFile file,
|
|
byte[] buffer, int bufferOffset, int size, long fileOffset) throws IOException
|
|
{
|
|
// If the content channel is not open for the file then start a transaction
|
|
|
|
if ( file instanceof ContentNetworkFile)
|
|
{
|
|
ContentNetworkFile contentFile = (ContentNetworkFile) file;
|
|
|
|
if ( contentFile.hasContent() == false)
|
|
beginReadTransaction( sess);
|
|
}
|
|
|
|
// Check if there is a quota manager
|
|
|
|
ContentContext ctx = (ContentContext) tree.getContext();
|
|
QuotaManager quotaMgr = ctx.getQuotaManager();
|
|
long curSize = file.getFileSize();
|
|
|
|
if ( quotaMgr != null) {
|
|
|
|
// Check if the file requires extending
|
|
|
|
long extendSize = 0L;
|
|
long endOfWrite = fileOffset + size;
|
|
|
|
if ( endOfWrite > curSize) {
|
|
|
|
// Calculate the amount the file must be extended
|
|
|
|
extendSize = endOfWrite - file.getFileSize();
|
|
|
|
// Allocate space for the file extend
|
|
|
|
quotaMgr.allocateSpace(sess, tree, file, extendSize);
|
|
}
|
|
}
|
|
|
|
// Write to the file
|
|
|
|
file.writeFile(buffer, size, bufferOffset, fileOffset);
|
|
|
|
// Check if the file size was reduced by the write, may have been extended previously
|
|
|
|
if ( quotaMgr != null) {
|
|
|
|
// Check if the file size reduced
|
|
|
|
if ( file.getFileSize() < curSize) {
|
|
|
|
// Release space that was freed by the write
|
|
|
|
quotaMgr.releaseSpace( sess, tree, file.getFileId(), file.getFullName(), curSize - file.getFileSize());
|
|
}
|
|
}
|
|
|
|
// Debug
|
|
|
|
if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILEIO))
|
|
logger.debug("Wrote bytes to file: network file=" + file + " buffer size=" + buffer.length + " size=" + size + " file offset=" + fileOffset);
|
|
|
|
return size;
|
|
}
|
|
|
|
/**
|
|
* Get the node for the specified path
|
|
*
|
|
* @param tree TreeConnection
|
|
* @param path String
|
|
* @return NodeRef
|
|
* @exception FileNotFoundException
|
|
*/
|
|
public NodeRef getNodeForPath(TreeConnection tree, String path)
|
|
throws FileNotFoundException
|
|
{
|
|
// Check if there is a cached state for the path
|
|
|
|
ContentContext ctx = (ContentContext) tree.getContext();
|
|
|
|
if ( ctx.hasStateCache())
|
|
{
|
|
// Try and get the node ref from an in memory file state
|
|
|
|
FileState fstate = ctx.getStateCache().findFileState(path);
|
|
if ( fstate != null && fstate.hasFilesystemObject() && fstate.exists() )
|
|
{
|
|
// Check that the node exists
|
|
|
|
if (fileFolderService.exists((NodeRef) fstate.getFilesystemObject()))
|
|
{
|
|
// Bump the file states expiry time
|
|
|
|
fstate.setExpiryTime(System.currentTimeMillis() + FileState.DefTimeout);
|
|
|
|
// Return the cached noderef
|
|
|
|
return (NodeRef) fstate.getFilesystemObject();
|
|
}
|
|
else
|
|
{
|
|
ctx.getStateCache().removeFileState(path);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Search the repository for the node
|
|
|
|
return cifsHelper.getNodeRef(ctx.getRootNode(), path);
|
|
}
|
|
|
|
/**
|
|
* Convert a node into a share relative path
|
|
*
|
|
* @param tree TreeConnection
|
|
* @param nodeRef NodeRef
|
|
* @return String
|
|
* @exception FileNotFoundException
|
|
*/
|
|
public String getPathForNode( TreeConnection tree, NodeRef nodeRef)
|
|
throws FileNotFoundException
|
|
{
|
|
// Convert the target node to a path
|
|
|
|
ContentContext ctx = (ContentContext) tree.getContext();
|
|
List<org.alfresco.service.cmr.model.FileInfo> linkPaths = null;
|
|
|
|
try {
|
|
linkPaths = fileFolderService.getNamePath( ctx.getRootNode(), nodeRef);
|
|
}
|
|
catch ( org.alfresco.service.cmr.model.FileNotFoundException ex)
|
|
{
|
|
throw new FileNotFoundException();
|
|
}
|
|
|
|
// Build the share relative path to the node
|
|
|
|
StringBuilder pathStr = new StringBuilder();
|
|
|
|
for ( org.alfresco.service.cmr.model.FileInfo fInfo : linkPaths) {
|
|
pathStr.append( FileName.DOS_SEPERATOR);
|
|
pathStr.append( fInfo.getName());
|
|
}
|
|
|
|
// Return the share relative path
|
|
|
|
return pathStr.toString();
|
|
}
|
|
|
|
/**
|
|
* Get the file state for the specified path
|
|
*
|
|
* @param tree TreeConnection
|
|
* @param path String
|
|
* @return FileState
|
|
* @exception FileNotFoundException
|
|
*/
|
|
public FileState getStateForPath(TreeConnection tree, String path)
|
|
throws FileNotFoundException
|
|
{
|
|
// Check if there is a cached state for the path
|
|
|
|
ContentContext ctx = (ContentContext) tree.getContext();
|
|
FileState fstate = null;
|
|
|
|
if ( ctx.hasStateCache())
|
|
{
|
|
// Get the file state for a file/folder
|
|
|
|
fstate = ctx.getStateCache().findFileState(path);
|
|
}
|
|
|
|
// Return the file state
|
|
|
|
return fstate;
|
|
}
|
|
|
|
/**
|
|
* Connection opened to this disk device
|
|
*
|
|
* @param sess Server session
|
|
* @param tree Tree connection
|
|
*/
|
|
public void treeClosed(SrvSession sess, TreeConnection tree)
|
|
{
|
|
// Nothing to do
|
|
}
|
|
|
|
/**
|
|
* Connection closed to this device
|
|
*
|
|
* @param sess Server session
|
|
* @param tree Tree connection
|
|
*/
|
|
public void treeOpened(SrvSession sess, TreeConnection tree)
|
|
{
|
|
// Nothing to do
|
|
}
|
|
|
|
/**
|
|
* Return the lock manager used by this filesystem
|
|
*
|
|
* @param sess SrvSession
|
|
* @param tree TreeConnection
|
|
* @return LockManager
|
|
*/
|
|
public LockManager getLockManager(SrvSession sess, TreeConnection tree) {
|
|
return _lockManager;
|
|
}
|
|
|
|
/**
|
|
* Return the oplock manager implementation associated with this virtual filesystem
|
|
*
|
|
* @param sess SrvSession
|
|
* @param tree TreeConnection
|
|
* @return OpLockManager
|
|
*/
|
|
public OpLockManager getOpLockManager(SrvSession sess, TreeConnection tree) {
|
|
return _lockManager;
|
|
}
|
|
|
|
/**
|
|
* Enable/disable oplock support
|
|
*
|
|
* @param sess SrvSession
|
|
* @param tree TreeConnection
|
|
* @return boolean
|
|
*/
|
|
public boolean isOpLocksEnabled(SrvSession sess, TreeConnection tree) {
|
|
|
|
// Check if oplocks are enabled
|
|
|
|
ContentContext ctx = (ContentContext) tree.getContext();
|
|
return ctx.getDisableOplocks() ? false : true;
|
|
}
|
|
|
|
/**
|
|
* Copy content data from file to file
|
|
*
|
|
* @param sess SrvSession
|
|
* @param tree TreeConnection
|
|
* @param fromNode NodeRef
|
|
* @param toNode NodeRef
|
|
* @param newName String
|
|
*/
|
|
private void copyContentData( SrvSession sess, TreeConnection tree, NodeRef fromNode, NodeRef toNode, String newName)
|
|
{
|
|
ContentData content = (ContentData) nodeService.getProperty(fromNode, ContentModel.PROP_CONTENT);
|
|
if ( newName != null)
|
|
content = ContentData.setMimetype( content, mimetypeService.guessMimetype( newName));
|
|
nodeService.setProperty(toNode, ContentModel.PROP_CONTENT, content);
|
|
}
|
|
|
|
|
|
/**
|
|
* Clone/move aspects/properties between nodes
|
|
*
|
|
* @param newName String
|
|
* @param fromNode NodeRef
|
|
* @param toNode NodeRef
|
|
* @param ctx ContentContext
|
|
*/
|
|
private void cloneNodeAspects( String newName, NodeRef fromNode, NodeRef toNode, ContentContext ctx)
|
|
{
|
|
// We need to remove various aspects/properties from the original file, and move them to the new file
|
|
//
|
|
// Check for the lockable aspect
|
|
|
|
if ( nodeService.hasAspect( fromNode, ContentModel.ASPECT_LOCKABLE)) {
|
|
|
|
// Remove the lockable aspect from the old working copy, add it to the new file
|
|
|
|
nodeService.removeAspect( fromNode, ContentModel.ASPECT_LOCKABLE);
|
|
nodeService.addAspect( toNode, ContentModel.ASPECT_LOCKABLE, null);
|
|
|
|
// DEBUG
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
|
|
logger.debug(" Moved aspect " + ContentModel.ASPECT_LOCKABLE + " to new document");
|
|
}
|
|
|
|
// Check for the working copy aspect
|
|
|
|
if ( nodeService.hasAspect( fromNode, ContentModel.ASPECT_WORKING_COPY)) {
|
|
|
|
// Add the working copy aspect to the new file
|
|
|
|
Map<QName, Serializable> workingCopyProperties = new HashMap<QName, Serializable>(1);
|
|
workingCopyProperties.put(ContentModel.PROP_WORKING_COPY_OWNER, nodeService.getProperty( fromNode, ContentModel.PROP_WORKING_COPY_OWNER));
|
|
|
|
nodeService.addAspect( toNode, ContentModel.ASPECT_WORKING_COPY, workingCopyProperties);
|
|
|
|
// Remove the working copy aspect from old working copy file
|
|
|
|
nodeService.removeAspect( fromNode, ContentModel.ASPECT_WORKING_COPY);
|
|
|
|
// DEBUG
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
|
|
logger.debug(" Moved aspect " + ContentModel.ASPECT_WORKING_COPY + " to new document");
|
|
}
|
|
|
|
// Check for the copied from aspect
|
|
|
|
if ( nodeService.hasAspect( fromNode, ContentModel.ASPECT_COPIEDFROM)) {
|
|
|
|
// Add the copied from aspect to the new file
|
|
|
|
NodeRef copiedFromNode = (NodeRef) nodeService.getProperty( fromNode, ContentModel.PROP_COPY_REFERENCE);
|
|
Map<QName, Serializable> copiedFromProperties = new HashMap<QName, Serializable>(1);
|
|
copiedFromProperties.put(ContentModel.PROP_COPY_REFERENCE, copiedFromNode);
|
|
|
|
nodeService.addAspect( toNode, ContentModel.ASPECT_COPIEDFROM, copiedFromProperties);
|
|
|
|
// Remove the copied from aspect from old working copy file
|
|
|
|
nodeService.removeAspect( fromNode, ContentModel.ASPECT_COPIEDFROM);
|
|
|
|
// DEBUG
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
|
|
logger.debug(" Moved aspect " + ContentModel.ASPECT_COPIEDFROM + " to new document");
|
|
|
|
// Check if the original node is locked
|
|
|
|
if ( lockService.getLockType( copiedFromNode) == null) {
|
|
|
|
// Add the lock back onto the original file
|
|
|
|
lockService.lock( copiedFromNode, LockType.READ_ONLY_LOCK);
|
|
|
|
// DEBUG
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
|
|
logger.debug(" Re-locked copied from node " + copiedFromNode);
|
|
}
|
|
}
|
|
|
|
// Copy over all aspects from non-system namespaces (we will copy their properties later)
|
|
|
|
for (QName aspectName : nodeService.getAspects(fromNode))
|
|
{
|
|
if (!_excludedNamespaces.contains(aspectName.getNamespaceURI()))
|
|
{
|
|
nodeService.addAspect(toNode, aspectName, null);
|
|
}
|
|
}
|
|
|
|
// Copy over all other properties from non system namespaces
|
|
|
|
for ( Map.Entry<QName, Serializable> entry : nodeService.getProperties(fromNode).entrySet()) {
|
|
QName propName = entry.getKey();
|
|
if (!_excludedNamespaces.contains(propName.getNamespaceURI()))
|
|
{
|
|
nodeService.setProperty( toNode, propName, entry.getValue());
|
|
}
|
|
}
|
|
|
|
// Check if the new file name is a temporary file, remove any versionable aspect from it
|
|
|
|
String newNameNorm = newName.toLowerCase();
|
|
|
|
if ( newNameNorm.endsWith( ".tmp") || newNameNorm.endsWith( ".temp")) {
|
|
|
|
// Remove the versionable aspect
|
|
|
|
if ( nodeService.hasAspect( toNode, ContentModel.ASPECT_VERSIONABLE))
|
|
nodeService.removeAspect( toNode, ContentModel.ASPECT_VERSIONABLE);
|
|
|
|
// Add the temporary aspect, also prevents versioning
|
|
|
|
nodeService.addAspect( toNode, ContentModel.ASPECT_TEMPORARY, null);
|
|
|
|
// DEBUG
|
|
|
|
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
|
|
logger.debug(" Removed versionable aspect from temp file");
|
|
}
|
|
|
|
// Copy over various properties
|
|
|
|
for ( QName propName : _copyProperties) {
|
|
Serializable nodeProp = nodeService.getProperty( fromNode, propName);
|
|
if ( nodeProp != null)
|
|
nodeService.setProperty( toNode, propName, nodeProp);
|
|
}
|
|
}
|
|
|
|
private void cloneNode(String newName, NodeRef fromNode, NodeRef toNode, ContentContext ctx) {
|
|
cloneNodeAspects(newName, fromNode, toNode, ctx);
|
|
|
|
// copy over the node creator and owner properties
|
|
// need to disable the auditable aspect first to prevent default audit behaviour
|
|
boolean alreadyDisabled = policyBehaviourFilter.disableBehaviour(ContentModel.ASPECT_AUDITABLE);
|
|
try
|
|
{
|
|
nodeService.setProperty(toNode, ContentModel.PROP_CREATOR, nodeService.getProperty(fromNode, ContentModel.PROP_CREATOR));
|
|
nodeService.setProperty(toNode, ContentModel.PROP_OWNER, nodeService.getProperty(fromNode, ContentModel.PROP_OWNER));
|
|
}
|
|
finally
|
|
{
|
|
if(!alreadyDisabled)
|
|
{
|
|
policyBehaviourFilter.enableBehaviour(ContentModel.ASPECT_AUDITABLE);
|
|
}
|
|
}
|
|
|
|
Set<AccessPermission> permissions = permissionService.getAllSetPermissions(fromNode);
|
|
permissionService.deletePermissions(fromNode);
|
|
for(AccessPermission permission : permissions)
|
|
{
|
|
permissionService.setPermission(toNode, permission.getAuthority(), permission.getPermission(), (permission.getAccessStatus() == AccessStatus.ALLOWED));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return the file state status as a string
|
|
*
|
|
* @param sts int
|
|
* @return String
|
|
*/
|
|
private final String fileStatusString( int sts) {
|
|
String fstatus = "Unknown";
|
|
|
|
switch ( sts) {
|
|
case FileUnknown:
|
|
fstatus = "Unknown";
|
|
break;
|
|
case FileNotExist:
|
|
fstatus = "NotExist";
|
|
break;
|
|
case FileExists:
|
|
fstatus = "FileExists";
|
|
break;
|
|
case DirectoryExists:
|
|
fstatus = "DirectoryExists";
|
|
break;
|
|
case FileRenamed:
|
|
fstatus = "FileRenamed";
|
|
break;
|
|
case DeleteOnClose:
|
|
fstatus = "DeleteOnClose";
|
|
break;
|
|
}
|
|
|
|
return fstatus;
|
|
}
|
|
|
|
/**
|
|
* Get the disk information for this shared disk device.
|
|
*
|
|
* @param ctx DiskDeviceContext
|
|
* @param diskDev SrvDiskInfo
|
|
* @exception IOException
|
|
*/
|
|
public void getDiskInformation(DiskDeviceContext ctx, SrvDiskInfo diskDev)
|
|
throws IOException {
|
|
|
|
// Set the block size and blocks per allocation unit
|
|
|
|
diskDev.setBlockSize( DiskBlockSize);
|
|
diskDev.setBlocksPerAllocationUnit( DiskBlocksPerUnit);
|
|
|
|
// Get the free and total disk size in bytes from the content store
|
|
|
|
long freeSpace = contentService.getStoreFreeSpace();
|
|
long totalSpace= contentService.getStoreTotalSpace();
|
|
|
|
if ( totalSpace == -1L) {
|
|
|
|
// Use a fixed value for the total space, content store does not support size information
|
|
|
|
totalSpace = DiskSizeDefault;
|
|
freeSpace = DiskFreeDefault;
|
|
}
|
|
|
|
// Convert the total/free space values to allocation units
|
|
|
|
diskDev.setTotalUnits( totalSpace / DiskAllocationUnit);
|
|
diskDev.setFreeUnits( freeSpace / DiskAllocationUnit);
|
|
}
|
|
}
|