From 556377ea3bd4b2a8c16d10c9b4702fa88162df93 Mon Sep 17 00:00:00 2001 From: Dave Ward Date: Fri, 13 Apr 2012 14:21:53 +0000 Subject: [PATCH] Merged V4.0-BUG-FIX to HEAD 34474: ALF-13169 Tomcat fails to shutdown - fix non daemon Timer's 34475: Part 1: Fix for ALF-13244 SOLR Multi-threaded tracking is required for performance - simultaneous document transformations - multi-threaded transaction and node tracking (off by default at the moment) - fix index/repo sync check failure if ACLs have been indexed but no transactions - minimise data sent back from query responses (not all stored fields) - added SOLR side config for HTTPClient pooling, cache sizing and tracker configuration - fixed SOLR incremental cache update for merges that end with all deletions in the old index - fixed unclosed stream in SolrKeyResourceLoader 34478: ALF-13050 - CIFS: Disabling account is not respected Also contains major rework of logging and exception handling. 34499: Fix for ALF-13150 34526: Fix for ALF-13288 34530: Minor CSS tweaks after changes for ALF-11991 34539: ALF-13176 - Implement Word for Mac 2011 Cifs Shuffle. 34541: ALF-13244 SOLR Multi-threaded tracking is required for performance - simultaneous document transformations - multi-threaded ACL tracking - multi-threaded statistics and reporting control - nodes that can not be indexed have an error record added to the index and do not block indexing the transaction (nodes unindexed due to exceptions can be found by ID query and the exception stored in the index) - nodes that are not-indexed have a minimal record added to the index for index consistency checking (unindexed nodes can be found by ID query) 34544: Add support for CIFS Level II shared oplocks. ALF-13138, ALF-13110. Fixed CIFS open for attributes only access preventing oplock on the following file open. Fixed reporting serialized copies of file access tokens as leaked. 34576: ALF-12767 - CIFS TextEdit - File has been modified outside TextEdit 34577: incorrectly checked in copy of network-protocol-context.xml 34580: ALF-13215: Ensure that permissions for everyone cannot be upgraded on moderated or private site. Fixed inconsistency between permissions shown in properties and in dialog 34582: ALF-13332: Updated modifier link for correct profile 34609: ALF-12740: Update to previous fix (only apply to IE8 and below) 34623: ALF-12767 - CIFS TextEdit - File has been modified outside TextEdit 34636: Fix for ALF-13365 SOLR: Recently modified docs dashlet sorts incorrectly - respect short property names on sort requests @cm:created and not require the full @{uri...}created 34659: ALF-2550 - added enterprise repo config files. 34715: Fix for __ShowDetails desktop action returned URL is truncated if hostname too long. ALF-13202. 34726: ALF-13293: Webdav: Version history lost after editing content in Finder 34738: ALF-7883: WebDAV: support HEAD method for folder - Fix by Pavel 34743: Fix for ALF-13244 SOLR Multi-threaded tracking is required for performance - simultaneous document transformations - batch fetch for nodes in transaction, acls in sets, and acls and readers - config for batch fetching - Better reporting for ACL set indexing 34747: ALF-13262: adding missing indexes for new schema's (activiti-schema create) + schema patch for existing schema 34817: Merged V4.0 to V4.0-BUG-FIX 34493: SPANISH: translation updates based on EN r34103 34498: Fixed ALF-12031: WCM: Content cannot be expired: avmExpiredContentTrigger is missing - Side-effect of ALF-11644: AVM cleanup jobs run when WCM is not installed - WORKAROUND: Get file 'root\projects\installer\wcm-bootstrap-context.xml' and use that 34525: Fix for ALF-13210: - removed "unsupported" from bulk filesystem import web pages 34531: Fix for ALF-13117 and ALF-13273 34549: Merged BRANCHES/DEV/BELARUS/HEAD-2012_03_15 to BRANCHES/V4.0: 34528: ALF-12874: 34552: ALF-13322: Fixed doc lib reload loop caused by "#" in folder name 34553: ALF-13311: Ensure images can be linked in TinyMCE create HTML content editor 34556: Minor: removed unused code 34557: Merged DEV to V4.0 34537: ALF-13035: Add "START WITH" parameter to IDENTITY field. ALF-13034: Add "optional" parameter for statement that drops index that was generated automatically. 34567: ALF-11047: Ensure that Explorer linked files and folders (from outside of sites) display correctly 34578: Fixes: ALF-11744: Dates rendered with the form service date control are rendered on the server, so show server time. - I've added the timezone to the display format and the ISO8601 date to the as an attribute on the HTML element to allow client side parsing - Adds client side parsing on the Doc Details page, so times are shown in the timezone of the user's browser. 34583: GERMAN: Translation update, based on EN r34103, Fixes: ALF-13075, 34584: FRENCH: Translation update based on EN r34103, Fixes: ALF-13002, ALF-13003, ALF-13020 34585: ITALIAN: Translation update based on EN r34103 34586: JAPANESE: Translation update based on EN r34103 34587: DUTCH: Translation update based on EN r34103, Fixes: ALF-12575. 34626: Fixes: ALF-13375 - Date rendering bug in search results 34630: Further fix for ALF-13375 that modifies Alfresco.util.formatDate's ISO8601 support for backward compatibility (e.g. passing in non ISO strings). 34635: ALF-12061: Mac support: Document Connection always throws an error - Case sensitivity fix by Pavel 34653: ALF-12308, ALF-12309, ALF-12554: Stack specific script errors 34655: Fix for ALF-12723 CMIS: Over-riding cm:autoVersionOnUpdateProps in custom model prevents startup 34656: Merged HEAD to BRANCHES/V4.0: 34654: Fixes: ALF-13389: Old element id used when setting event end date. 34657: Translation updates for all languages except JA. 34660: Fix to license driven config files to remove erroneous characters 34669: Merged DEV to V4.0 34663: ALF-12242: User activation issue InviteHelper.acceptNominatedInvitation() method was changed to enable user account in any case(no matter was it enabled/disabled before) 34681: Merged DEV/THEMIS2 to V4.0 34472: Document List Customization Refactor - SLingshotSiteModuleEvalutaor now has new param that defaults to false for backward compability - Slingshot extension points, surf-doclist.get now uses 2 spring beans: * "resolver.doclib.doclistDataUrl" to get the repo doclist data url * "resolver.doclib.actionGroup" to get each item/nodes action group id 34692: Fix for ALF-12715 - Incorrect SPP working (mimetype not set on document stored via ADM Remote Store API) 34708: ALF-13239: Merged V3.4-BUG-FIX (3.4.9) to V4.0 (4.0.1) 34707: ALF-13239 Share rule to convert to PNG fails on JPG images - Issue was showing up in 4.0.1 as a change was made for iPad that introduced an imageOptions.isAutoOrient() setting. This forced a concatenation of null with " -auto-orient". However there are also crop and resize options that could also do this even in 3.4 Setting the commandOptions String to "" when null, is fine as this is how property value nulls are handled later anyway. 34718: JAPANESE: Localisation of Company specific contact information & addition of timezone to form control. 34719: FRENCH: File consistency tweak. 34746: ALF-12903: Create HTML content fix 34754: Merged PATCHES/V4.0.0 to V4.0 34750: Reinstate ${version.label} into version.number property 34810: Merged DEV to V4.0 (with corrections) 34807: ALF-13290 : Mac Support: Error appears after collaborator saves changes to the document deleteFailedThumbnailChildren method should be run as system user as it may fails with AccesssDenied if collaborator updates document 34876: Fix fo ALF-13503 Add SOLR client API tests to the SystemBuildTest project - SOLR API tests run embedded with SSL 34984: ALF-13109 - Correction to NTIOCtl.FsCtlCreateOrGetObjectId 35009: Merged BRANCHES/DEV/V3.4-BUG-FIX to BRANCHES/DEV/V4.0-BUG-FIX: 35008: Fix for ALF-12817. Fixed as suggested - new method remove(). 35031: Fix for ALF-12309 35032: Fix fo ALF-13535 using CMIS, on-disk tickets cache can grow unbounded - expire tickets based on inactivity by default - added job to clean up expired tickets - all are configurable 35033: Fix fo ALF-13535 using CMIS, on-disk tickets cache can grow unbounded - avoid NPE for null tickets 35037: Fix for ALF-13505 SOLR tracking readers does not encode all uids correctly - fixed reader encoding 35049: ALF-13384 - Saving large Word (mac 2011) document via CIFS fails in Mac OS X Lion 35053: Merged V4.0 (V4.0.1) to V4.0-BUG-FIX (4.0.2) 34844: Merged V3.4-BUG-FIX (3.4.9) to V4.0 (4.0.1) 34843: ALF-5830 show_audit.ftl template doesn't work anymore - Removed L10n messages that are no longer used (should have been removed in 3.4.6 when this issue was fixed) 34847: Merged HEAD to BRANCHES/V4.0: 34804: Fixes: ALF-13309: Issue with over zealous HTML escaping with truncated descriptions in the Calendar Agenda view. 34861: ALF-13497: Merged PATCHES/V4.0.0 to V4.0 34813: ALF-13115: No feedback is given to the user when Approve/Reject is clicked for a task when they followed a link to the task in an email. - Fix by Pavel, reviewed by Kev - Now they get a confirmation message followed by a redirect to their dashboard 34862: Fix for ALF-10823 "allowGuestLogin=false" and Share then fills the alfresco error log with "Guest authentication not supported" Fix for ALF-12678 Errors in log on startup (ts.alfresco.com 4.0) - improved handling of 500 errors relating to GuestAuthNotSupported when alfresco.authentication.allowGuestLogin=false 34867: Merged DEV to V4.0 34565: ALF-13074: JBPM workflow definitions are not resilient to missing model definitions WARN messages have been added if JBPM workflow definitions cannot be loaded in the model definitions. 34855: ALF-13074: JBPM workflow definitions are not resilient to missing model definitions Reimplemented to handle all exceptions during constructing WorkflowInstances WorkflowTasks and WorkflowDefinitions. 34859: ALF-13074: JBPM workflow definitions are not resilient to missing model definitions Logger messages was changed to correspond the logger pattern. 34893: Translation updates for DE and ES. 34894: Fixes: ALF-13518; Updates Calendar event object's URL to work out of context. 34896: FRENCH: Translates new strings. 34915: Merged DEV to V4.0 34912: ALF-13267: There should not be a web-client-config-custom.xml in alfresco.war Move "modules\quickr\config\alfresco\extension\web-client-config-custom.xml" to "modules\quickr\config\alfresco\module\org.alfresco.module.quickr\ui\web-client-custom.xml". 34913: ALF-13267: There should not be a web-client-config-custom.xml in alfresco.war Delete "modules\quickr\config\alfresco\extension\web-client-config-custom.xml". 34916: ALF-13267: Merged V3.4 to V4.0 (and reversed previous duplicate fix) 24828: Merged BRANCHES/DEV/BELARUS/V3.4-2011_01_13 to BRANCHES/V3.4: 24824: ALF-6361: web-client-config-custom.xml doesn't work in /alfresco/tomcat/shared/classes/alfresco/extension 34929: ALF-12242: Issues activating users when more than one member in the authentication chain - Correction to fix that caused regressions ALF-13494, ALF-13498 - Need to check for the mutability of a user's authentication before trying to enable it - Also chaining of the authentication enabled attribute should assume true until false found, not the other way around 34930: ALF-12242: Reverted change to this class as it wasn't necessary and wouldn't work! 34932: ALF-13453: Enable XMLConstants.FEATURE_SECURE_PROCESSING feature on Transformer Factory to prevent remote code execution - Now SecureTransformerFactory should be used as a standard 34965: Merged PATCHES/V4.0.0 to V4.0 34959: ALF-13550: Fix for ALF-13546 SOLR tracking fails for nodes with content and no auditable aspect - NPE as there is no last modification date to use 34960: ALF-13551: Merged BRANCHES/DEV/V4.0-BUG-FIX to PATCHES\V4.0.0 - fix for ALF-13544 When SOLR encounters an error indexing a document, subsequent indexing does not occur 34541: ALF-13244 SOLR Multi-threaded tracking is required for performance - simultaneous document transformations - nodes that can not be indexed have an error record added to the index and do not block indexing the transaction (nodes unindexed due to exceptions can be found by ID query and the exception stored in the index) - nodes that are not-indexed have a minimal record added to the index for index consistency checking (unindexed nodes can be found by ID query) 34968: ALF-13453: Reversed XSLTProcessor and XSLTRenderingEngine changes for now as they break http://wiki.alfresco.com/wiki/WCM_Forms_Rendering and model handling via bsf extensions. A more sophisticated approach is required. See bug for more info. 34972: ALF-13340: Upgrade postgres JDBC driver to tested/supported version! 34997: ALF-13453, ALF-13565: Fully reverted revision 34932 as it prevents startup on Weblogic 34998: Merged V4.0-BUG-FIX to V4.0 34992: DUTCH: translation updates based on EN r34861 34993: FRENCH: Translation updates based on r34861 34994: ITALIAN: Translation updates based on r34861 35013: ALF-13561: Not found error after uploading new version - Fix by Pavel 35034: Fixes ALF-13570: Error loading event info panel. 35039: ALF-13573: Merged V3.4-BUG-FIX (3.4.9) to V4.0 (4.0.1) 35022: ALF-13451: Allow modules to configure mimetypes 35041: ALF-13466: Error is displayed by approve or reject wcm workflow - Fixed regression caused by ALF-4098 - Protected calls to new addNewChildrenIfAny() method with isDirectory() checks 35042: GERMAN: Translation updates based on r35029, and fixes ALF-12471. 35043: SPANISH: Translation updates based on r35029, and fixes ALF-12471. 35044: FRENCH: Translation updates based on r35029, and fixes ALF-12471. 35045: ITALIAN: Translation updates based on r35029, and fixes ALF-12471. 35046: JAPANESE: Translation updates based on r35029, and fixes ALF-12471. 35047: DUTCH: Translation updates based on r35029, and fixes ALF-12471. 35090: Remove Kofax. It has been migrated to integrations/kofax 35097: Added new file server cluster tests. Open for attributes only overlapped with open with oplock. Open with oplock with break to level II shared oplock. 35099: JLAN Client updates to support level II oplocks, required by new cluster tests. 35100: Various oplock related fixes, including problems opening file on second cluster node. ALF-13109. 35107: remove errant '>' 35116: ALF-13401 - Mac LION Powerpoint CIFS 35162: Removed spurious attempt to force a concurrency exception for getNodePair after a node had actually been deleted. Code would retry 50 times before failing. Reviewed with Derek, its not the node service's job to second guess that there may be a concurrency problem in a client's cache. 35164: Fix for ALF-13641 - Negative cases for date value in propertyNegative cases for date value in property. Today button 35169: ALF-13401, ALF-12393: Added exception translation to AbstractReindexComponent retrying transactions, following change in r35162 35172: ALF-13626: category.put.json.ftl has wrong bracket 35173: ALF-12749 - CIFS: Editing of ppt/pptx files fails (MacOSx specific) 35174: Fix for ALF-13556 - Sorting for custom model fields doesn't work for search results in Share 35176: Fix for ALF-4281 - Script error at 'Email space users' form 35186: Merged BRANCHES/DEV/DAM/V4.0-BUG-FIX-34847 to BRANCHES/DEV/V4.0-BUG-FIX: 34875: Creating new branch from $FROM 34939: Merged BRANCHES/DEV/DAM/V4.0-BUG-FIX-34397 to BRANCHES/DEV/DAM/V4.0-BUG-FIX-34847: 34400: Creating new branch from $FROM 34422: Merged DEV/DAM-0.1 to DEV/DAM/V4.0-BUG-FIX-34397 34085: Allow for generateThumbnailUrl to accept a rendition name parameter. 34086: Changed simpleView view type switch to integer implementation rather than boolean. 34087: Pulled specific rendering code for simple and detail view into separate view renderer objects. 34092: If simpleView was stored as a boolean convert it to an integer for ALF-12952. 34423: Merged DEV/DAM/HEAD-34276 to DEV/DAM/V4.0-BUG-FIX-34397 34307: ALF-12952: Change DocumentList simpleView Nav Switch to an Int Implementation 34957: ALF-12952: Change DocumentList simpleView Nav Switch to an Int Implementation - Removed ability to specify index on registerViewRenderer - Added firing of setupAdditionalViewRenderers to make it easier for extensions to register themselves at the appropriate time 35021: ALF-12955: Share Document Library and Repository Browser Should Easily Allow for Additional Views - Changed viewRenderers to an object implementation with storage/retrieval via named properties or 'keys' 35050: ALF-12955: Share Document Library and Repository Browser Should Easily Allow for Additional Views - Renamed simpleView preference and option to viewRendererName - Reintroduced simpleView boolean preference and option as deprecated to allow deletion of old preference - Renamed viewRendererOrder to viewRendererNames - Added default viewRendererNames at DocumentList.options level - Renamed widgets.simpleDetailed to widgets.viewRendererSelect but did NOT change HTML id for backwards compatibility - Renamed onSimpleDetailed to onViewRendererSelect - Added deletion of deprecated simpleView preference if it exists 35056: ALF-12955: Share Document Library and Repository Browser Should Easily Allow for Additional Views - Made viewRenderer methods a proper Alfresco.ViewRenderer object which is more easily extended - Added name property to ViewRenderer constructor and changed registerViewRenderer to use that as a key - With more strictly defined ViewRenderers in place, changed select button to iterate over viewRendererNames rather than explicit list 35104: ALF-12955: Share Document Library and Repository Browser Should Easily Allow for Additional Views - Added markup tag around the document list container 35126: ALF-12955: Share Document Library and Repository Browser Should Easily Allow for Additional Views - Added markup tag documentListConstructorSetOptions around setOptions after DocumentList object constructor - Added markup tag documentListViewRendererSelect around view select buttons - Added markup tag documentListShowFolders around show folders button - Added markup tag documentListSortSelect around sort selection buttons - Renamed Alfresco.ViewRenderer to more specific Alfresco.DocumentListViewRenderer and private methods similarly - Added default for viewRendererName if it's undefined in options - Added check for availability of renderer specified in user preference, if not use default, and consolidated renderer index lookup 35179: ALF-12955: Share Document Library and Repository Browser Should Easily Allow for Additional Views - Removed documentListConstructorSetOptions 35194: Temp disable cifs text edit test. 35197: ALF-13097 - IMAP templates have wrong mimetype 35201: Merged V3.4-BUG-FIX to V4.0-BUG-FIX 34462: Merged DEV to V3.4-BUG-FIX 34461: ALF-10759: Advanced search fails for sub-element tags UITagSelector component which allows Advanced Search to add new tag option to search 34479: Merged V3.4 to V3.4-BUG-FIX (RECORD ONLY) 34477: ALF-13237: Yet another 13th hour Spring Surf Regression - Can't afford to pull in all the latest surf goodies so overriding PageImpl.class with one corresponding to Surf revision 1034 in WEB-INF/classes, just for 3.4.8 34515: ALF-9855: Alfresco side to support standard Adobe-Japan1 PDF fonts in swftools - Bitrock binaries provided 34518: ALF-13266: Ubuntu installation fails in non-obvious way when machine lacks sufficient memory - Fix from Bitrock - L10N required 34536: Merged DEV to V3.4-BUG-FIX 34529: ALF-13135: Impossible to Add new member on Workspace using email address NPE fix if AD users don't have e-mail address as a property. 34538: ALF-12812 Saving files with apps on Mac OS X Lion in CIFS doesn't invoke rules (Update rule fires BEFORE, FileFolderInterceptor recalcs HIDDEN and TEMPORARY ) 34542: Add support for Level II shared oplock. ALF-13093, ALF-12328. Fixed CIFS open for attributes only access preventing oplock on the following file open. 34543: Oplock and open for attributes fixes to the repo/AVM filesystems. ALF-13093, ALF-12328. 34579: ALF-13284: Removing obselete files 34603: ALF-10833 Alfresco does not show correct thumbnails for some specific kind of PDFs - Patched PDFRenderer-0.9.1 to return a null page if there was an error. The code structure did not lend itself to simply throwing the exception. - Modified PdfToImageContentTransformer to check for a null page and it then throws an AlfescoRuntimeException which causes the failover transformer to use the next transformer in the list: PDBBox which is able to transform the pdf and the image that was missing. 34617: Add missing source Java folder. 34629: ALF-13188: Content IO Channel not closed 34697: ALF-13149: Start up performance suffers if the alf_transaction table grows too large. 34712: ALF-13063: sample settings for DB2 34803: New installer translations from Gloria 34809: ALF-11956: Merged BELARUS/V3.4-BUG-FIX-2012_01_26 to V3.4-BUG-FIX (V3.4.9) << In addition to the 2 merged revisions, includes the change for ALF-11972 and test all-widgets.xsd >> 33715: ALF-11956: WCM accessibility - sandbox name oriented titles were added almost to all action links at 'Browse Website' page view; - adding titles to image tags functionality was added to ActionLinkRenderer, UIMenu and UISandboxes (this includes arrow icons for 'Web Forms' and 'Modified Items'); - titles were added to XForm Date/Time picker controls (text input and arrow buttons); - 'Click to edit' functionality via keyboard availability was added to XForms TinyMCE editor control (using 'Tab' key, 'Alt' + 'E' in IE or 'Alt' + 'Shift' + 'E' in FireFox); - additional i18n properties for Date/Time picker and action link titles were added 34625: ALF-11956: WCM accessibility Increasing XForms widgets readability by screen reader tools: - Tiny MCE 3.2.7 buttons; - required fields; - inputs labels; - VGroup, HGroup and Repeating widgets folding icons/buttons and others ALF-11972: Title attributes for the WCM form element xs:anyURI not included to allow multiple xs:anyURI file picker "Select" buttons to be distinguished by screen readers - Change defined in JIRA 34846: Translation updates: - FR: Missing Strings - DE: Fixes encoding issue 34881: ALF-13512: Merged PATCHES/V3.4.8 to V3.4-BUG-FIX 34829: ALF-12621: Sort order of folders including hyphens ( - ) are different in folder-tree and view on folders (in Share) - Switched from using JS sort to Java locale-based sort 34845: ALF-12621: Fixed array typing problems in previous checkin 34918: Fix for ALF-13385 Access DENIED api does not seem to work - changed default behaviour to any-deny-denies - config to switch back - needs custom port to 4.0 for SOLR - unit tests added 34919: Fix for ALF-13385 Access DENIED api does not seem to work - added property based configuration and default configuration check 34937: ALF-11956: Merged BELARUS/V3.4-BUG-FIX-2012_01_26 to V3.4-BUG-FIX (V3.4.9) 34886: ALF-11956: WCM accessibility - headings functionality is added. WAI-ARIA markup was used; - alert for XForms validation errors is added. WAI-ARIA markup was used; - previous accessibility changes tested and fixed against the new functionality 35003: Merged HEAD to V3.4-BUG-FIX 34673: Changed from time-based module and component names to GUID-based names. Not likely to affect anything. 35057: Fix for ALF-12590 Share - Document library doesn't return subfolders when parent space contains the character "- " - updated to the latest version of jaxen (which now includes saxpath) - the problem path is now parsed correctly 35074: ALF-13597: Merged PATCHES/V3.4.6 to V3.4-BUG-FIX 34978: ALF-13489: Index tracker now has ability to distinguish create/update/rename/link/unlink - Will prevent unnecessary cascading PATH regeneration on remote cluster nodes - QNames and noderefs of parents in index compared with those in the database - Experimental - needs testing 34983: ALF-13489: Correction to renamed node detection 34985: ALF-13489: Even more foolproof parent assoc cross-referencing - Should handle duplicate QNames, etc. - Renames now just an add and a remove 35075: ALF-13598: Merged PATCHES/V3.4.6 to V3.4-BUG-FIX 34872: Merged DEV (by Pavel) to PATCHES/V3.4.6 (and refactored) 34554: ALF-11777 : Persistent lock is left on document in certain use cases when editing online (spp) 1. From now documents are locked for maximum 24 hours when working through WebDAV/Vti. 2. Session listeners were added for web-client and vti-module to allow handling session expiration event. 3. WebDAVLockService class was implemented. It is used by session listeners to perform session cleaning (forcibly unlock all documents that were persistently locked during http session). 4. LOCK/UNLOCK webdav methods and Get/Checkout/UncheckoutDocumentMethod vti methods where updated to correctly populate session list of locked documents. 34832: ALF-11777 : Persistent lock is left on document in certain use cases when editing online (spp) 1. From now documents are locked for maximum 24 hours when working through WebDAV/Vti. 2. Session listener was added for webdav/vti to allow handling session expiration event. 3. LOCK/UNLOCK webdav methods and Get/Checkout/UncheckoutDocumentMethod vti methods where updated to use shared code to lock/unlock nodes. 34833: ALF-11777 : Persistent lock is left on document in certain use cases when editing online (spp) 1. Remove unnecessary classes after 34554 rev. 34852: ALF-11777 : Persistent lock is left on document in certain use cases when editing online (spp) 1. Some changes after David's review of revisions 34832, 34833. 34874: ALF-11777: Fixed typo 35078: ALF-12785: BaseDownloadContentServlet could co into an infinite loop if asked to seek past the end of a file 35079: ALF-12490 "HTTP Status 500 - 00200935 Exception in Transaction" message error with webform - ALF-9524 fix assumed there were only switch elements in a form 35086: ALF-13563: Upgrade to Bitrock 8.1.0 to fix password validation issue 35095: ALF-12764: New distributable alfresco-enterprise-ear-3.4.9.zip - Like war zip, but contains .ear file instead of .wars and also contains WAS shared library - Means samples and other bits are finally available to non-Tomcat users 35103: Merged DEV to V3.4-BUG-FIX 35098: ALF-12776: if a user requests to join a moderated site, and that request is rejected, the rejection email is sent to the user-id and not the email id. Implemented Correct WorkflowModelModeratedInvitation.WF_PROP_REVIEW_COMMENTS field in configuration for moderatedInvitationReviewTask Person's email into emailAction PARAM_TO 35114: ALF-12766 Creating Web Content several users - different sandboxes - To be consistent with ALF-11440 PM comment 18-Dec-2011 and ALF-8787 A Manager should only be able to create a file in a sandbox if it is NOT locked somewhere else. - Not much can be done about the error message as the locked path is useful in other situations and it is not possible to issue a different message on create only 35121: ALF-11956: Merged BELARUS/V3.4-BUG-FIX-2012_04_05 to V3.4-BUG-FIX (V3.4.9) 35109: ALF-11956: WCM accessibility - Date/Time Pickers are made accessible via the keyboard and readable by JAWS (13, demo version). WAI-ARIA standard is used; - corrected 'expanded' state determination for Date/Time Pickers; - Modified Items and Web Forms arrow buttons are made accessible via the keyboard on the Browse Website page; - some changes per the description of the issue and per the comment of the 23-Feb-12 11:33 AM 35145: ALF-11990: CIFS login with case insensitive username is rejected - User name normalization moved to before MD4 hash retrieval 35151: Port of oplock related changes from v4.x. 35177: Fix for ALF-11936 - RSS feed from the activities dashlet produces invalid XML 35178: ALF-12631: removeChild requires delete permissions on the child node, even when it is a secondary association - now it doesn't (thanks to Andy's solution) - new ACL_PRI_CHILD_ASSOC_ON_CHILD ACL entry only enforces the permission on the child node when it is a primary association 35181: Merged DEV to V3.4-BUG-FIX 35165: ALF-13409: Invite to a site throws an error if an instance of invitation-moderated-workflow is started by a user whose account is subsequently deleted InvitationServiceImpl listens for person node deletions (it already implements beforeDeleteNode) and cancels invitations within beforeDeleteNode 35182: ALF-12567 Unable to create thumbnails for certain PDF files - The supplied PDF contains an invalid offset in the xref table. This turns out to be a quite common error resulting in thousands of Google hits. The offset is set to the string value "4294967295". This number in hex is FFFFFFFF. The value of an 4 byte int in C or Java with this value is -1. Neither PDFRenderer nor PDFBox have workarounds for this although lots of other systems do, which is why it is possible to view or edit it in other systems. Patched both PDFRenderer and PDFBox to handle this common error. 35185: ALF-13033: Friendlier error message when you try to delete non existent content from a sandbox 35191: ALF-13409: Fix build. 35192: Merged V3.4 to V3.4-BUG-FIX 35161: ALF-13624: Merged V4.0-BUG-FIX to V3.4 34474: ALF-13169 Tomcat fails to shut down - fix non daemon Timers (and punctuation!) 35163: ALF-13656: Merged HEAD to V3.4 31375: Fix for ALF-435 - Unfriendly error occurs when trying to delete renamed category from category page 35189: Italian translations from Gloria 35193: Merged V3.4 to V3.4-BUG-FIX (RECORD ONLY) 35125: Merged V3.4-BUG-FIX to V3.4 35156: Correction to merge in revision 35125 (a reintegrate merge rather than a selective merge) 35202: Merged V3.4-BUG-FIX to V4.0-BUG-FIX (RECORD ONLY) 34532: ALF-13233: Merged HEAD to V3.4-BUG-FIX 32960: ALF-11008 - Support the WebDAV DELETE method in SPP/VTI, with the special response required by SPP for locked documents 34559: ALF-13106: Merged HEAD to V3.4-BUG-FIX 28223: Merged DEV/SWIFT to HEAD (Tika and Poi) 30589: Upate Tika and add Ogg Vorbis support + tests 30673: Upgrade POI and Tika for recent fixes 31009: Bump the Tika version for some recent fixes 31010: Update the test audio files to include more metadata 31011: ALF-6170 Add missing audio model (needed in devcon demo) 31013: Update the MP3 extractor to output audio keys (related to ALF-6170), and refactor the audio extractors to share more common code. Also expands the audio extractor tests to share common code, and test more metadata. (Needed for devcon demo) 31022: Tika update for custom mimetypes enhancement 31023: Add @since tags where known, and do a quick coding standards sweep 31274: ALF-10813 follow-on - make it clearer that we're just creating the one detector, and switch to the new style version 31289: ALF-10803 - Upgrade Tika to add the extra WordPerfect mimetype 31553: ALF-10525 ACP mimetype detection fix, unit tests for it, and a NPE fix 31554: Update Tika to get the fix for TIKA-764 32105: ALF-11574 Upgrade Tika for the fix to TIKA-784, and add the DITA types to the Alfresco mimetype map 32138: Bump the Tika version for the updated TIKA-784 fix, and add an Alfresco side unit test for this case 32153: Update the vorbis jar to one that includes the license info more clearly in META-INF (without needing to read the POM) 32320: ALF-11650 Upgrade Tika for TIKA-789 (MPP Detection), and add tests that show it is now being correctly handled 32363: Update POI and Tika for the new code required to solve ALF-10980 (MPP Open/Change detection) 34560: ALF-13106: Merged V4.0-BUG-FIX to V3.4-BUG-FIX 33330: ALF-12487 In Mimetype Detection, if Tika detects a generic type of text/plain or XML, defer to the Alfresco filename based type (as we already do for octet stream) 33379: Add the TIFF mimetype 33380: Improve the stream to Tika conversion code, following review for THOR-952 33385: Upgrade to the latest Tika and POI, for recent bug fixes 33779: Upgrade Tika for ALF-12714 33782: ALF-12714 Add 3GPP/3GPP2 video, and MP4 Audio mimetypes 33783: Update Tika for more MP4/QuickTime support, and enable MP4 audio metadata extraction + "quick" testing 34561: ALF-13106: Fixed merge errors 34562: ALF-13106: Merged SWIFT to V3.4-BUG-FIX 26546: Have one copy of the Tika Config in spring, rather than several places fetching their own copy of the default one (either explicitly or implicitly). 34563: ALF-13106: Merged HEAD to V3.4-BUG-FIX 32264: Adding "quick" test resources for MS project. 34564: ALF-13106: Fix unit test 34752: GERMAN: Translation updates, based on EN: 34612 34753: SPANISH: Translation updates, based on EN: 34612 34755: FRENCH: Translation updates, based on EN: 34612 34756: ITALIAN: Translation updates, based on EN: 34612 34967: ALF-13552: Merged V4.0 to V3.4-BUG-FIX 34932: ALF-13453: Enable XMLConstants.FEATURE_SECURE_PROCESSING feature on Transformer Factory to prevent remote code execution - Now SecureTransformerFactory should be used as a standard 34971: ALF-13552: Merged V4.0 to V3.4-BUG-FIX 34968: ALF-13453: Reversed XSLTProcessor and XSLTRenderingEngine changes for now as they break http://wiki.alfresco.com/wiki/WCM_Forms_Rendering and model handling via bsf extensions. A more sophisticated approach is required. See bug for more info. 34982: ALF-13554: Merged V4.0 to V3.4-BUG-FIX 34972: ALF-13340: Upgrade postgres JDBC driver to tested/supported version! 34999: ALF-13552: Merged V4.0 to V3.4-BUG-FIX 34997: ALF-13453, ALF-13565: Fully reverted revision 34932 as it prevents startup on Weblogic 35000: Translation updates for DE, ES, IT. Based on EN r34846. 35015: ALF-13451: Merged V4.0-BUG-FIX to V3.4-BUG-FIX 33864: ALF-10736: JSF - Adding mimetype does not work on 3.4.x 35020: ALF-13451: Merged V4.0-BUG-FIX to V3.4-BUG-FIX 33863: ConfigSource for XMLConfigService which uses a ResourceFinder for wildcard-compatible lookups (UrlConfigSource does not support them) 35029: JAPANESE: Translation updates based on EN r34846 35212: ALF-13409: Deleting a person can now cancel their invitations. Cancelling invitations can delete inactive persons! So prevent infinite looping with a transaction local resource - Also fix up other invite related unit tests 35217: Merged DEV to V4.0-BUG-FIX 35214: ALF-12745 : AD-LDAP: alfresco hangs when upload user csv file Disable 'Upload User CSV File' button in Share admin console in case of AD-LDAP 35221: Avoid a NPE if Repository.getPerson() is called when no RunAsUser is active, instead return Null as for users with no defined NodeRef git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@35229 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- config/alfresco-global.properties.sample | 6 + config/alfresco/bootstrap-context.xml | 1 + .../bootstrap/imapSpacesTemplates.acp | Bin 22389 -> 21067 bytes config/alfresco/cache-context.xml | 34 + config/alfresco/content-services-context.xml | 1 + config/alfresco/dao/dao-context.xml | 1 + .../Schema-Reference-ACT.xml | 10 + .../Schema-Reference-AVM.xml | 17 +- .../Schema-Reference-ACT.xml | 4 +- .../Schema-Reference-AVM.xml | 19 +- .../ActivitiTaskIdIndexes.sql | 26 + .../ActivitiTaskIdIndexes.sql | 26 + .../ActivitiTaskIdIndexes.sql | 26 + config/alfresco/ehcache-default.xml | 7 + .../node-common-SqlMap.xml | 39 ++ .../permissions-common-SqlMap.xml | 24 + .../messages/action-config_fr.properties | 16 +- .../messages/bpm-messages_de.properties | 12 +- .../messages/bpm-messages_es.properties | 12 +- .../messages/bpm-messages_fr.properties | 13 +- .../messages/bpm-messages_it.properties | 12 +- .../messages/bpm-messages_nl.properties | 1 - .../messages/content-model_de.properties | 3 + .../messages/content-model_es.properties | 3 + .../messages/content-model_fr.properties | 3 + .../messages/content-model_it.properties | 3 + .../messages/content-model_ja.properties | 3 + .../messages/content-model_nl.properties | 3 + .../messages/patch-service.properties | 2 + .../messages/patch-service_de.properties | 6 + .../messages/patch-service_es.properties | 6 + .../messages/patch-service_fr.properties | 6 + .../messages/patch-service_it.properties | 6 + .../messages/patch-service_ja.properties | 6 + .../messages/patch-service_nl.properties | 6 + .../messages/site-model_de.properties | 5 + .../messages/site-model_es.properties | 5 + .../messages/site-model_fr.properties | 5 + .../messages/site-model_it.properties | 5 + .../messages/site-model_ja.properties | 5 + .../messages/templates-messages.properties | 11 - .../messages/wdr-messages_fr.properties | 4 +- config/alfresco/node-services-context.xml | 3 + .../alfresco/patch/patch-services-context.xml | 11 + .../public-services-security-context.xml | 14 +- config/alfresco/repository.properties | 19 +- config/alfresco/scheduled-jobs-context.xml | 27 + .../default/network-protocol-context.xml | 22 +- .../default/swf-transform-context.xml | 2 +- config/alfresco/version.properties | 2 +- .../auth/cifs/CifsAuthenticatorBase.java | 59 +- .../cifs/EnterpriseCifsAuthenticator.java | 546 ++++++++++------ .../auth/cifs/PassthruCifsAuthenticator.java | 163 +++-- .../filesys/auth/cifs/package-info.java | 26 + .../alfresco/filesys/avm/AVMDiskDriver.java | 4 +- .../alfresco/filesys/avm/AVMNetworkFile.java | 2 +- .../filesys/repo/ContentDiskDriver.java | 31 +- .../filesys/repo/ContentDiskDriver2.java | 32 +- .../filesys/repo/ContentDiskDriverTest.java | 248 ++++++- .../filesys/repo/ContentIOControlHandler.java | 54 +- .../filesys/repo/ContentNetworkFile.java | 19 +- .../filesys/repo/ContentSearchContext.java | 27 +- .../repo/FilesystemTransactionAdvice.java | 74 +-- .../filesys/repo/InFlightCorrectable.java | 30 + .../filesys/repo/InFlightCorrector.java | 47 ++ .../filesys/repo/InFlightCorrectorImpl.java | 110 ++++ .../filesys/repo/LegacyFileStateDriver.java | 41 +- .../filesys/repo/TempNetworkFile.java | 3 + .../ScenarioCreateDeleteRenameShuffle.java | 114 ++++ ...arioCreateDeleteRenameShuffleInstance.java | 295 +++++++++ .../rules/ScenarioCreateShuffleInstance.java | 55 +- .../ScenarioDoubleRenameShuffleInstance.java | 1 + .../ScenarioLockedDeleteShuffleInstance.java | 21 +- .../filesys/repo/rules/ScenarioOpenFile.java | 2 +- .../repo/avm/AVMLockingAwareService.java | 22 +- .../alfresco/repo/avm/AVMSyncServiceImpl.java | 4 +- .../repo/content/AbstractContentReader.java | 2 +- .../PdfToImageContentTransformer.java | 5 + .../ImageMagickContentTransformerWorker.java | 4 + .../repo/domain/node/AbstractNodeDAOImpl.java | 39 +- .../alfresco/repo/domain/node/NodeDAO.java | 26 + .../repo/domain/node/ibatis/NodeDAOImpl.java | 32 + .../permissions/AbstractAclCrudDAOImpl.java | 34 + .../repo/domain/permissions/AclCrudDAO.java | 9 + .../repo/domain/permissions/AclDAO.java | 11 + .../repo/domain/permissions/AclDAOImpl.java | 40 ++ .../permissions/ibatis/AclCrudDAOImpl.java | 40 ++ .../invitation/InvitationServiceImpl.java | 79 ++- .../repo/invitation/InviteHelper.java | 27 +- .../org/alfresco/repo/jscript/ScriptNode.java | 22 + .../org/alfresco/repo/jscript/Search.java | 3 +- .../org/alfresco/repo/model/Repository.java | 7 +- .../FilenameFilteringInterceptor.java | 80 ++- .../repo/model/filefolder/HiddenAspect.java | 10 - .../node/cleanup/TransactionCleanupTest.java | 242 +++++++ .../node/db/DeletedNodeCleanupWorker.java | 98 +-- .../node/index/AbstractReindexComponent.java | 242 ++++--- .../index/FullIndexRecoveryComponent.java | 141 +++- .../index/IndexTransactionTrackerTest.java | 2 + .../org/alfresco/repo/search/Indexer.java | 25 + .../repo/search/IndexerComponent.java | 11 + .../repo/search/NodeServiceXPath.java | 7 +- .../repo/search/impl/NoActionIndexer.java | 10 + .../impl/lucene/ADMLuceneIndexerImpl.java | 84 ++- .../impl/lucene/ADMLuceneSearcherImpl.java | 5 +- .../impl/lucene/AVMLuceneIndexerImpl.java | 10 + .../search/impl/lucene/DebugXPathHandler.java | 8 +- .../LuceneAlfrescoXPathQueryLanguage.java | 29 +- .../search/impl/lucene/index/IndexInfo.java | 4 +- .../search/impl/noindex/NoIndexIndexer.java | 18 + .../search/impl/solr/SolrQueryHTTPClient.java | 2 +- .../authentication/TicketCleanupJob.java | 55 ++ .../impl/AbstractPermissionTest.java | 5 + .../impl/PermissionServiceImpl.java | 500 ++++++++++++++- .../impl/PermissionServiceTest.java | 606 ++++++++++++++---- .../permissions/impl/acegi/ACLEntryVoter.java | 146 ++++- .../security/person/PersonServiceImpl.java | 18 + .../repo/solr/SOLRTrackingComponent.java | 10 + .../repo/solr/SOLRTrackingComponentImpl.java | 47 +- .../FailedThumbnailSourceAspect.java | 18 +- .../repo/workflow/jbpm/JBPMEngine.java | 71 +- .../jscript/JscriptWorkflowInstance.java | 20 +- .../service/cmr/security/PersonService.java | 9 + .../DynamicallySizedThreadPoolExecutor.java | 156 ----- .../alfresco/util/TraceableThreadFactory.java | 109 ---- 125 files changed, 4517 insertions(+), 1194 deletions(-) create mode 100644 config/alfresco/dbscripts/upgrade/4.0/org.hibernate.dialect.Dialect/ActivitiTaskIdIndexes.sql create mode 100644 config/alfresco/dbscripts/upgrade/4.0/org.hibernate.dialect.MySQLInnoDBDialect/ActivitiTaskIdIndexes.sql create mode 100644 config/alfresco/dbscripts/upgrade/4.0/org.hibernate.dialect.PostgreSQLDialect/ActivitiTaskIdIndexes.sql create mode 100755 config/alfresco/messages/site-model_de.properties create mode 100755 config/alfresco/messages/site-model_es.properties create mode 100755 config/alfresco/messages/site-model_fr.properties create mode 100755 config/alfresco/messages/site-model_it.properties create mode 100755 config/alfresco/messages/site-model_ja.properties create mode 100644 source/java/org/alfresco/filesys/repo/InFlightCorrectable.java create mode 100644 source/java/org/alfresco/filesys/repo/InFlightCorrector.java create mode 100644 source/java/org/alfresco/filesys/repo/InFlightCorrectorImpl.java create mode 100644 source/java/org/alfresco/filesys/repo/rules/ScenarioCreateDeleteRenameShuffle.java create mode 100644 source/java/org/alfresco/filesys/repo/rules/ScenarioCreateDeleteRenameShuffleInstance.java create mode 100644 source/java/org/alfresco/repo/node/cleanup/TransactionCleanupTest.java create mode 100644 source/java/org/alfresco/repo/security/authentication/TicketCleanupJob.java delete mode 100644 source/java/org/alfresco/util/DynamicallySizedThreadPoolExecutor.java delete mode 100644 source/java/org/alfresco/util/TraceableThreadFactory.java diff --git a/config/alfresco-global.properties.sample b/config/alfresco-global.properties.sample index ae0cbdce3c..2d71726f19 100644 --- a/config/alfresco-global.properties.sample +++ b/config/alfresco-global.properties.sample @@ -63,6 +63,12 @@ #db.url=jdbc:postgresql://localhost:5432/alfresco # +# DB2 connection +# +#db.driver=com.ibm.db2.jcc.DB2Driver +#db.url=jdbc:db2://host:50000/ALFRESCO + +# # Index Recovery Mode #------------- #index.recovery.mode=AUTO diff --git a/config/alfresco/bootstrap-context.xml b/config/alfresco/bootstrap-context.xml index d985d9d7b8..aa0959bf98 100644 --- a/config/alfresco/bootstrap-context.xml +++ b/config/alfresco/bootstrap-context.xml @@ -134,6 +134,7 @@ + diff --git a/config/alfresco/bootstrap/imapSpacesTemplates.acp b/config/alfresco/bootstrap/imapSpacesTemplates.acp index 9e245719511e87e1f3407c8fc0d99345de9177e7..680891c37eb41ff3202a4853c0a273628777ce73 100644 GIT binary patch literal 21067 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 literal 22389 zcmbTeV{~PUwuKwpwrv|#so1t{+qP}1V%tut;#7=^ZCCVG-E;2Q_1@dJwR`uE{9UbO z_13@9`xtXBc_~mZG$0@#D4+_TK>^uY{1ihlARr4RARtU2JRl1j1A8TV10xeBWfL2F zYXfH!CkA&LYZYZ^AW;9?BbncSF7B{Ez+m^lKtTWXLFZX|a%)Uz!&}d28pBpu=Qb6q z`4XTJ{AUI;hZAHB?2hka9Lcda8;4}{GTOClGM6f(*u^L)Yoqt~aoJz=o$qm+E<<3( z=Zx21IG$E-%~48`r3gm5$KPZ=ye?_lu)3K#unrh=ZS%%eb<(@}Ox3 zo#P|L1ba_lGfNV`D$~7rFT=|DCFe+_zV+H=izdHxK${UOc&eJ(wI}D@FUp|BOvW0# zYVDV|y5pe8cKZra$%S3pZ>-Z|P-e3dyiqXYKi-k!ppFpE znRBb$shDKf8PgmJ&-6=Q>v9V-OV6f_MTbh-%GsfzDP@cHk8KfLHW5!-=0-M5{%YG4 zt=*3&&n8b6l}><^bus!jeqQYTQZ+GdN-|k4ADwwS8TK8rlRgwa*}b$xc7Jc`j8q4t zzoBcUe?RNHxcI*RFxfmT=BbO)FRgEe2sCe--5NAqhA!PbZqF^7lO5yHd`|beRnjVE zW+99n73^-j^| zmF6v}Au?tv360$aKXht};JAwn{7um?6WG6C^A)=Uwq@?R^lgi>t8ZGD*jQ4<6-<`G z=KSaw%)E_Tj(`tn;J-jdVAj7hU-}gIt&%8Jto1Y}%#wZ}Yf}QDXY~wtFpGfBz%e#q zbE1Reqr2BSZBtU?=bb7t_u5OAfgir9e!9j68H|Of3$g)g^7gJ4W_)FqzL>Vx4$cs# z{!i;>o7Yc2q1LTCem+;T)^Wo|Rf74Yi2 zEA``{B$jli{kFO+HznM8r8)r;)*3msvbhd-drAdb^!R{UN?IOpHXJ%vM~m2t*?vQ@wou9E4srJ3z+PgV5HQ-Jww>Laj%%~^ zG1Or?T4xBcHot5fLT@RPOmuL)BuZEJTNE+4T2+Ln9;QsO^un;QOjQXZgfbO$gqPt= zo_;QZ@0~m>Aur{&LG;68acS9Ptxg!(Hy#I4wodgO(2==14)$5c1q2(xEeou&GaXgD z7OmA1mPy*k4G}M4vA&lj#JJtw4^lj)mM`^wzRSjKJZNSaW!^` z<1Iwrkj$A<4IZ{IFsX6&BD6n@v5(oz46Q`2EhrNVX31dkwYh|H9Fo)r;lh}C@sZ&f z_aV&H1SGEC9d_8fqr8I3{|8xz;r#-qTD#nepg=(2us}epe~&Ci6B`2yYePF@4?SlS zcV}~F8*4oSYg0!PCnGxsQ|Eu-tik9&1~*Dd_Wy&kYE=chH4Y@-3;#DrRq9Ze@bAkS zv!4^tja?hzr4|caG|v(+@)bj-gm|YqFu_2Ej=eN>8;iDw%xyAFOaFK&LbWk4>}#T=m{{5m zu3KlzehGH(&!K1o(fdLSMbr7*w9D5a7}5l|s_8TS7%Q^xzmiJG@QSyEjqgFxS}H*3Cg-#HKXU#4#}k@1k*Wdn&=_*^IZHmY1_1 z;meKU8oT<4;x;SZ_Gu@U;qi7koOlCpWbjGhk1YH1OpSPH1$qd3OojAFYIMta)T!=x1_ zV$UOX3A0tZk+C?&X<@S-YU@E>2eR3@oac#7AcvpecRJ zp}Xd?E2>?TTsR4Z6$kwoT|pdPPBvSsLo?z-s!mHk9OCn45=3i1JjNZ`I(d@Ecrc^{ zg};awMElaAx_00Pg!xotACPAR9EAQX8*t`kAJb|{@ssGbYtf74=JN&njnkN)qnHhD z7jFU*$qVVTRVrO>s)RSu=Z7mw&`JE1R& zh`JjpX^#V&=Iw>xSy9lUlB1d%I9E;sO5c94@QG$wBgy3GNgct2P_0b8&+aba zEr(Vu->jv6Q5}A9(GnkiLhZg?ts)onY9bgufQ=5r%*z`yxs+`_X}YGtrDe#R0ZM4T=xU%K3YMbN@`>?cQ8aDJz-4<*$KKS4;|&Ou5<)2GJP&UqRusO@C!^2D?E-_&)GXl-MrEGS zcNE$D@)b^On@)U*4m{^8GY(w)-G<)Vfso~0Ob#>=2KM5Rqmd(@#STKcLC-;up$HrFy$^wgTWuNUL9=et~Tp z3}vw6T^>&k5vxn0gw1I`bsU9gp5#YBAY5VRqGH5zD2ebr#oJ&iuJMfK-aR~eL5$*Q ztB~m)u;d_``cU=C3@j+}LmF_u(5=;OVPhm7l(3F$><_Bq^bbgDR7PSY31bO>ka{sz z?B7yRJ8hubN%v)zy^*t=3hb72eD%=Rf!4$c&w|uDOl9SbJYKGV*BH!Bm;%<#3`38U zP;L%5JuJ698cOl~@w20&MQKina~DJ_oX;=l?fy%2==4SUMCD}aX*)Mb!2xEatYl3c zG4}AHQ$y@pwGd@{{)jUuBejgw#Wk=-U#ezGK0V}o!#WICC^hF|v2OgkWR)x`--v{Y z-tt@p+?+rw(GFR|_i@FzJDOjwy3`FF+aLpd9}W-Q-=#g8U~mnhc?<7RZj;mHX1$98 z%#7$J@C&?ZdW1kYyf#z9s(&80bEGQ-$kdZYYX`l^R)yA~uApf0LE$dfR~mndY8G0# z*h4Fy0*W%E`~c~l21SU2(hqoxN3wh>8@f{Tdq2I+tD6nVS|lSm51Nv5y5YHHzCO_G z&ZLO)u-Ay`lG_dJN+gXO-{Qik=wF5l7HKDPW(~H_?fN18OGD=B^2=a8E?;M*z=M)k z^)O$e^FSDuW3q*dRi7Nfj|*2=>>|`M++sNn8Inv@>63x?ffZ7}xA(ayZ%n7@4fmY& z$h*()UMH$BBG{iv;;yDF8{w%}3-5_I2#HT~yxqyts>7NU%t#Sy>52|$F@JnCegOaF zc@L9Of!ucl!bE`Q5#aur=K&?;zo+&C!o~mKdD~rp=P{3X>m~}#qsAh07&NIG0;_VG zqA1P{PtOX)H1XDk6pJP2V!HzfzlIf#5_pb*-lOv*lWVn@aJi;INJp^VUS|m^e!RcQ zIdjW^XZUkykGCvYXM2Dd7h4YdBj&Pe8iP+$2J63{&Sk}0QQl*f4`n4Llk8VyY2diseq_#T>W&3i z{@$w*jxUjJb2T^QD}44FxP+)OZrwR}36=tld}8U$gD=o;%hN)s8RqwsMRtWqW5*6; zVA^TG@F*FvVL57SykDptDt@&)lLo3Et0PLqQW~?rL(fVWy_uw+*&CFvtT*;844&H_ zml0r~;R*RkN2t;dMEwMs>BtPb6=147H%~kHm5W^&XUqQ}h~8&VP-xlObiTw`SHQbb z^jxVZF4O9YflC10o9YWNXea2g3BBSeRPikgN^G*_cO&u4t?JrCeK?d5_P}8RL(@;N z-=8?(rCvaAs9yBLP0EIO2ZiSNoJQI@a;-~{zhBZ?so)sKLk)`Q@9migRPeD(XuE|p z123;0JI8!~$lcc5K;x^c0h@#i-v6R<7{JO41MqxixxshwQ@D?bF!)G?_*Q`D^UaUO zQhYam@_g8h6jvrXMA8?JsVSED!0UyXzw9KSpTaM>I)Q!JPcfjSa9d7-(^}@z_*Q+e znItT4w5m(aV4{|L1*|b(s%i4u^L6VmZNUJ~51|WIk~Z+fG9@K-4b@eu91M4t37)B! zGr`a~&9w=6q5Ad6!&EV>_%mI7w7e)j$hg)+3+ z=ldS|I4naGxUpeSsG;&b7u4C0KL z^Nw@(lleSVzQa^;`6>E2%m>3#{GQL}i>*z9JyDa(YmRyN;{@Y%X8$D!et%dcL`A9z z%fi+#r{cDk1zX*oZa|GW8r46(nlx7!J$jkO5M=JEeA&iiPWEn9dt{0ZyjewvM7;X| z`ilUaUgH9{?6$3e00LMoT_#Xo7EdLKvf5Y*V4XMX0)D7tZ8zP;;R&lDYEtZ#@ z8$1+a3Quv${;PVHu#@q)xMSwbIOslh+fNe2A=)~}Gv((Z8Saic!kukv3dkALk()Tw4kT5Y#b-a(G8vs4*Tfw)})j90gfOfq~Wn?6_7WR%}Ce=;A{8xe)AS<(vtfb#z0$gUu5001=N z&#KUzOJTH`cILL;AJohgtN#rEl2ja}@j7_4tc0*%(=s!6gW{D!4WbpnbF5FQlba{lV#BAs3>78$Il!m9ObKo zXfU{Q;Nm5(gjL2*Jdg2pCd#|qqQ&=<2j0*lL@HRMSZBz&aOMG&JyL^n0sa&d0$7GC z7WdOk@odu@hS?i@00LAo%P&^Y2~i@4egP1`WyPC-+k~OBup41{<=8ps`$g`o=(;i= z6Mz8l!uNek?T6oENj24*N3D)n=L81j_?HW@!c1`5yb&scV|9+wpliO_(Gv5gU|^Rb zJquNwNug{Hs3BHmT6bh;kpd!ojV0xtYkOTXHFX*+lkBEz;+%IPjHNW?6`oSrJ~Uw> zyK{{S!m1;a%(rTPsdPspnH}tt*5wnjk)rHm=1`038=_;U$^c;p^9)o9f?J!XXG!X& z4hm4f*k3(&;%0gt=CBP{6)>nh%uG|?xD9-vUhA$F2!wpNd`%`q2&B~M)|Q72@tuoR z4(eoRv3asP+TZbL>tn$7rJr^k*t>#gb-zaN_{H~20}$k{^!fz{17UuVQ`}Kw?tFT0 z^OO;qamj!}fSiL^f zoeX0aS9q`vwab$#M9Hgcm@nCRK+N2gYvyFr|B|Re#|;rwlv0*cT(^r2!9tPcE67Vi zv)ISQ#w7DX_gkcntJa(ua+~3kC(F;+J_by~cKhi%(l}1^s9cIHNpgJS!Im)b+a4 zKkB+)?|V}Aw9{wPe0a46+$JqbVBieuk^MH@bvTAWHDGSJJOK&cqmNnK*ZX()XB> zk=ii#8C&1Of5ydtv|B@*bF%MRBU%mqWR5!xK#zJu?$cBTu8r{3>9 z!!%#U2w2qib?yZ#U)4v0N1tI7rvV~9XuK>x86NkKwm4>skA@E2K;|*DMJNofXkJjw zpRsmxbN=G&cYZhHunN`ge81luWdcsH=#efT3G*<@K8bipo7*{ zx-YbB7o8auQ#%`*)yS9hvL%9_7S*PN9#O2?yk6Fi*E(-Uq@T+n)QkM<9ncw`6*k>8r=&?|rB#=^7+=xLP(oUV z{U;)ieG@svFt9l>o8xzC_aeYDG%37Ej)yUI@38mo8ER#+RSwJgJ^^Qo$yy`_B>RjD6t+G1$;r%s(Ba7=f`&Il{dP2Ac{`H9tHmz@8GVJ0uJ-lc9Q_JTm3_` zTN?bw>^SHTe;@F*?AC?Re8qMI;bcRAhELBzVfNhdOC-J^Q!AUyM~-StpIww2#qqLs zuGlmwHbblmpxuXL{RDSNph6=+LfI*N#hO>ydVA>o8ksc za(wsGjrbyjl*ov3%M>YEj6ww>sHjX8tJW5qUW)>kk73NNw3f}aDj)lEmJY?vj-Z-z z-Bk}SRE?J1b?pu3?CRCU0{v#?&uer8>kbUPRv9t$_&3(2>3z{iH$oN6mKOMHcdl73 z6^o_eY%xtbr^5?E{o6`E=9((;8~xzH>FLma{P-d%kdKy6f;U%jj}cQoq&WsM0AVLy z0TNI%uTBLqkh?E;zSl;%T30Y$SomCdiJRu=Q{t6P0uLuP1X3c(g5~nr(_GMxh0)%= zl3Ik9#FeNm{$kFRV-bdjv|o}VG7v((4wuWg>u@ta@h23cop|VT!$3jEa3OcvV<&CLy|QJRN0UqgNjY zQF9_9F`^Y6E^Hd^>?bz(nA7akPp(qLGO1$IU?>s|%hsW-yJ7TX{wpp8%}_c}C?s{o zVDviQMH9rLBfc`YnF5v4(Z?lZv>eCoPliM8p^w%V+V)VLoG}+_G1yu$lUgFNDr*F2 z^`z$?acf|wd%B8(pJyyg!)Tj6HpGSIZQ%OeLwfEX7lp|Y@_GLViNSn`fLbM9P#Is) zCXD}d>#jB|mt;5B|2cDADyi4N0F6d8`8f_GHl;!{WNgo+eF*0WiZ_*{RFSnpGSfnK z$s&!q`Zcg@c-!LG%RV)SZalw6#QHgO_-7|udme>gdu~yL!Zj>{G|n+04Y%e&GL_mL zo^=o250x&ChztKwCvV2!V>)E_ho{4B%)IZn-THfYBuo}gxtEj!zdqh?@2}pA%-jSs z4?huoeb$yv%nRdteSO(Z3kvC@)}XXsqRW;{JXGg?GI`ot1Ahv;%CKef#DQcv8pgva z$QurFzUmx5=+9%wG#P5L*~`C?+`kv_JXA!+pisKd&>F`Dm4xgqfz~E8dqkYytJi?@ z&DkJnkj>x>D;gSCkLk~6duGFbg#u5ba4R^(8yyVFLzTc)IUVqQV4V5UnfRCk<))5m zxV=!atki_XPB+;kb_(VeU9+F(QF*8@Xv8A`s?M5@Gp4$ zUg$Gvgx-z-;Nb-i1jO{e-v~OH8#w;83;Z9p`IBlkPCFb(@40#fJQih>U}y~87R)2x z(HYKVk~oq`eV!MP7eO+i##4m-O`k4xRt|RboygjcqfCK3x?j#3D}P7|ckfFl@-nHK z|A5z=ocH&aWW%(agSQPmjIjfqi$W}%h7f!V>(%QfR|j&d?#Io@aMQMAs?3GE ze=cj-nBs~pymaM;?crhu0}}xul29QwmuQzK=zDflr|rzrM1W(=Hcf~>*|QR-eVy|{ zmYX<>$w=Vofx>9`jeqenTu1+7ejihr4D`?76`do-`$ zaAuJ?UCe>ws8P_i>P&{~D~hcBA&_IHeBgQ8K^Zmpt1;hW<|j~My4dnqR~&&rVXfi17K*id(OayEH2yPQkxzG zK@-0cPe96|Ut%zQmB<&9g*H|*>*Q$p__!{CllY9MIU4<9R4{q2egck6lhc_Lxf~c=EP4T(g{=>+ z>64*JF9IMZ2=ZhmL<>viW5d`Z^wtX8{Z@54oHI+F!6~ucuhKh}a8lmi; zcIMRuHrd;X*m3d)wyC;WktQU3;n!uR4Lws5uG^3k-DNARV5$HM@%IshQ zx)2u*-|!Yb60;pB%oB#St7TXhoS`pvn4${~9}Em3b?`k?DO81f(w{V+z|{206J&U` z^3ir*-1AG4yNK5NgYJ9srD6rR?=)Kp<@#jz+TvVd4%Yl~4?!+Xbb)&Pe|h;KEEc?gCd$w8_}t%TzH-rQZ?|rZ`6{kH zvGeD*AtiKOURr{!tQCS=OAgm+81Rh*)n%_hG3r2!Z2Uah+;M?n=`QJHc9c=#0FLA& z%NKj3?oqmkY(H+FZG5C^IwkTAP!QJ0_Lz=(0v)FLk#!nHdBf(}URc$(q>u_z!3L?R z{cP@mT{qRgMYV)}%=<1*K_F;q(;@KF@7v4!clbLYiOHuACKvui7gK}}8wgkw_K2#j z;3lqbjUTwzTYvGw6>vc)vJSAi2k;^lz>ELG0r0F>`6lWOOVI~-_ld1dlB zP4lK8Fj*H(Si9dT$kwDAJ2R5+cGbkxF=vERwI#ro;il(Wk6_QD?kCcRfE(*pj>AlW zG#2?E*DEVYk@4jeB`*5Z3siW`7Z5=~XlAv`Kl8R7=PvaijyZIp`pkN`t-hU~M8o0Z zXm4a|>t-kBiykwIl_k4&;NP4gZg8f7A&W0DnS^{1rl98c!j0EI(#BBaA&5KOE$ZL^ zUF|MKjr(ymBGP&wndF~ecRsW-t`44LeW0@>6rb=i0opu|EnrO za-~ZdHW*okU^6Hrs#v#26xkP3y0!z{cVj=W5s#qRX8idpuLe0J5N)Wc5(n;5%| zJ8r#ji(;C@JqIX(S^VRN%21N-nsR>~*EKP{TBgI(d z@l}c_on3KG|e#27JaD3<$K%K=<51I2?1(B zLjtclb_)^lSFFd7Nw_+Y>JI7ZRKpTcM*uoRKue;hn(1zMwo@kMKQ_W8#I|A;lL>C_ zwa`lJgql`64|&ERPj$;pp@LX3MWZrDF1jj=ZXnoWSQnbZF~0C781XAt3bRBBX-Qsd z*B9!{QX=ZI6tObN`d9OAli>{ttDEhPnt!8rq=>aIxVV9j(=2Z!x`2UXi7wxL*U^SC z!1m_I6Gk?f0xJ%gw!cp~S*dCUlVt9RgVdB9XC2`Gd|CiJi)*lA6W+0R+-Ow$@N$h- z6%sogJ*+HAw+l~9)&_n>rCr#Iyo{V`3l9H^b&8oSp7zbCI}TO96jB`)ccskzqhF_y z{|)lU283!R54qp!cDokQPpAac&lp7#CycQtCxOt5T>SMxYt2nkQN92s?HT&YH#rfO z-M9_wbb(lq21Z9+8L>l)3YF?+%3fv4DL9Q8^v$)-&F(rA{Mp8O$2K~9HX7c4XA=5o z(CB>wA4$$KIvN4={LnHEk$-umXfNr8--Np-*{Pv zu<+ood+pcbFrzKPd>v1B|CLVb?(+R3^>K3i^r${D(;-mfHLv6%y*UTF-!H2_P*{Yd z(v6fp_W89|{FU=B!khr^6${jjIA;LDwEYvp{Bf)pL;Txg#iZJX;|btcF|AZCyK2D- z9Fml4;SkMQ7T;u+*#scWVFhUwI9md_8VYdcc7o^ZG3nlL2MYzWzARVvahxeggSyYl zx#hhFYK{zmF#Q0+(3&sA{hKhfZRffRJ#b?hU6?)#&pjJ&`@gc`@NL_A-cD|8vcT9S z{dg>tR*zlBrnj7H8K6nxYYZk}!osB$J>PXc1r|7Ih#$N2)x^o2tiqW;hRX7(4%#u4 zva79X9L7Q(al3`nrP!$Sv1J0*er~p~(W73$@l@cW;heSocBBqlFsqu}@->S&rbzL| z?+Y+^(s=i4auO`YBY{?vMX|{H*b?<F5GD}^Dk0zkjm1l;=#J4R7CHYkd{3aMzM2&YU{a9N?CtCrv8X_uVj zlH3C{ejT66QU(u+O5!xo?9?~@e(IepuVe}#v_BO7p(V|+BZcL^W5tahgaxOwJfE1? zLm1S2OMVIoKn$bq(=g9(#7qGY(>3yM#3Ut-tJ`zJWlhF$a@oIv{~Iw~0K~k%{}*CX zpXUZA#T>+2VGOr;OxJ6TY%YE&#zn}ljNw<)YsJSaSG^3FLZ}C*>XfQV)vJ;ELN++DlD`++#{l~>#V!X z+%BZCTW*3uAJyDFR!=~qN|GT`Pha9uqdD7iltjsyF^i2w*|(Tuk20lCQq_3vG_;S* zjVk7@{`g!X#=oF}If{TPugXx+okC6gs{lvm`x-T0rkb*a!1x1o+Z&xz{b*eBMl! zS+7oD&8!EOx6bz`;4^;8a$Ncj&loN1*C)aYyMxaml04l0=wTv=>_f4R43YS%8NZhG zO~)OVKSNQqDInP-kY+Lf1D}MR5JeZ_3jWC2=AK?p6ZKnMd$Ahj&#jvtDT;1?gWchG zvj{#TdNa|ht%F7)g@nZuSg9ycq7O6jcig|g(f~MvxNRvj909;m_)oy{#|>oW?{6TJ zYL|fMg7lI5-_a#)vkA+HB1Fn-zId{@c**w!$_iK}cu$h#6Dyy5E-B+;3Ufbjl70Iz z;pEWR&4ry`r)D*Wf)!8V9!4~}CVPb8k;Mf=i~=288(YiKRc%GXn2E=qBYp$PcM1>^!ZXCdzu#r>%wvL zVw`p>)-Mzpr7o<{Lk0covW7PCvxBqq-l7*bm>*M#Spl;~!?Y2ptHCd3E9O94L+$?=YdP!?Ch4`Zr^S{ z1hafWa1X7RhgRUtL6L$ihr%yF^LG+O5{0PhR%242?+EW7KGZe(S#`iI}Oxp{d$J| zcNv$}9qFKTQ+7WCD$n8X%ENY>%*emqgC6EgN%>H1H1ftgdr-=F9}JZeyD>Y`01hoN z-XUjg>c4NkZ(-SthRdCZ9B!`4R+l_0LLcRwbM-s8R1E{Dat$Sg_^fz5t44O$0qMr4 zSN%(pT?`LR$C5q?r!1;zWYzWB{t|0Rl7Br&-S|}6nvVdOR>5wIN1GYK$bkqU3Ll(1 zb&(Y>6| zG=5N6vT+p>>#k-g&x!LSxms$g%@lxeOx>5ToT_m<|XUrDq+0RY|J)uh?n>d*I zB&@{osd?AdPU-dkI697Q=z1)oLszyyV6%g@aFAnq>37SyjrsBTI-cqND^umhvFwBX zlu_(5nG_Os5A=?!^Q~8;b~tk?db=0iP_~dPbw4ceK+X5TPU6qB;s&_1va%Oq&j+!H@_ynI z_B*HWp31MDyNq96dM>E}n$YyWn~=|o(x|O)^e)xiUXVU6wNu94LDE4;~x;F(hAq_r26H*fE z_KG4S{oRB}PO%hJ#Uo2*5PX6D)r2q|_4_C?*M&+GIyqR46TLJ!lRn`=(Cv^dLU8kU z{mPskGy1P4B*^Aj^WZaTlQzlljjcEJ^mgZ^4UXfJYcnvQ35E0q0-8_~ENMDl zz535ga8ihBcqdwcPs`@U^D=u1MR4b+Uzw>xX_A@je^A-c$m^c5evD(W^iLJS#`c{794-jV2 zFF}~ToibGnAswyg7J0VUS5Jv17z6t<FRHmpy~rKR@mVjXBRC#Gzfua9Tc63cq|%Fv(=7TJv4U28siNAyX+0qpyeSXmf!!g9ELkuR#(xwXDZ%5IJ%lBVWP+t3qW6}qs#V@Ht%g^Q#P;R+ zp{Sr6Z_oMo?mu;C=)4zp9nBy`CYX|RqkS3 z<=9wFK}BLwq*!}Ns|y?0+-2CfAj4l_j%JNQd#T+9*N^6o?l85RZUH+MCBTkFOponN ztb%sJtyPWD(CW@vRD&5=@n0nfE~=^POAqiH9nY4g&-Bvb&@Ur~%O4gWTCDLf0TpP_PiqKJf#@sasOTXAUh`6(?EXqB@PLaB`~l$+Kj6iXp?{K8{y68* z{QWrxP=V}lMUeca_3E`j0;wKG;L-OSd&iS7 zdKK zl((E)A)M}u&Qt(3JtHGxNm659o8T9y>r8#D`cc@ciykg2UMm(hq)ed_)POG)gJlgy z`8MU~dc;2@lc7W{@SWI)e~F zpqF}9X!1hy<8>ywjT!vX&(mW7 zVHcTk*a8~X7{jQc>JJ2uL0~YKFKl6P)sA}XqfaS(;BqWV4rvKRQztqp!2LA2SEN1l zx!u_|xkMs4)8BuW9RD_38812NJ9OFz5M^UpQo?5&78Ohbq3GpHa|aS%AQ!0TU^y+K z6ZqYW3IE#cxc;HH#BtbItk#G_0Zxf3tZ;j|!}M;h{=pqcy1AdR7#XDW{W60(y1qAH z;Gua1MnlY_9wM<3`NGDxfFP5p;*`ypBlt{naI7r)-EuJ5qm2BAE`2;b$$gNLmU{^m;^R=s$oWyC=|HLl^i8wx3F9l<*!y1YT96WpMxT=tcC|3N z?RMTL+dhI$6$a0d1&yZKVlS?cMCyo%q1gC(M#=_s@T;Q@l$su#8(9%L{I3x&-?^U7 ziW-qU|w|N|H-y=?u=nYHvaqeJUH~K^7`SLz>aiVivsl+xS zPhQV(iHyJHK|J>42Ku0`5lQDd&B(=<73Br$`TMQE{0OzbeAGsN_#r_>O|R?OBBi@I z>Az#$xW>RjZqUFJ-}WfAJ~|yX{tEc7tuVXuZ0z^cBKI~D7Q+?r$fe_e6WTr;)?Wx% z887rk=bVPN26bgVSD$^yw>%plvRGU-6$977sppZmuJWJ47k$z)Z%MbXrdsjF=X6=R zW+d8EIG|h&BmM}#9bb2A;Vj#$H17POv5KLH0gWSxz;=#h@leCW*~d}55wT(Qx}3}P zp051tJ@`R?uH=ry*#4-q)8q9ZGT(bI=)6Nii`$I&QO&4?!p$%_t&zzAJ}$`c{>M7_ z_xWfwj@g2ql8 zr02p;2U&p7TXfc&KV7tdYLM?RoE8)#_I|)JezqSMA;~zGL<@yc-2pAR`vu!EI~0TC zINRlqly&Vs?6f+nEU8)!C&tgbj>a_zx+E$mu_GHK`=!m|t#0NBdKJBPclg@X{I>K1 z0`x@sdcvOi*V7RjF5@<|p9*$3q{;zIMuO!!hgCo+Ir3CI6l-1Q_C-*)_)l)J!}r$W zGk<~*r*gaA?L5KD<#}`765{Ue_D;Ja7V_ZlOAZa;s2!fMX}Dg=18pC4Y2f;gdl^~y z-&N6NsFrj6mgAXJ8WxY13?_6er323K*Su`>)O=8OYi^f?;rk2)iWqQ zlL%BOM1_ouq0Ye|G*_&(hRSF7RtzM zw`UjEg@)?R*Ai*w`3&)E93a`P9X?B*pbnLl#rlXA6DCf|reON7Kf69H23zJ!Na5|$ zQdf(%u#?6#q*d0|tVU3wyV7Uf;BnfQ;*QVAi-kQiSA>6^NjB@HrYOx|QdBpcei?MG z>K6C$OR}I$9Goal9eWuSixxW6wi(6|{uPI}>tHP_Gf6>7#U*^p$G&f;mO0u)wpY`! zkH>aqf8p^~?>p)WiJw=9K7hn9#aK&oHSmjZIrS1^6P4~D?k5Cd>wDG3WSKzK1ll?s zvf?AdSCoD(GmRe;f81BKO-BV*w?0h}1Jt1Zr)vDM(acBs+l^**;WrUf$bdfQJ~kl~$5c;c>u7Z}3hq#j6d1blk|-r%=ovlLAabNqB?S1$)I={c9v zwMjt)O9nJv3s4|RNRhQH$*MXaeb-ixcgBeZ+dN^et~nD2C*i5RBf2#zv8yOJ(}^+; zfz!rS*qu{_-i?*c>DFVIr@84kXHlzzre_qU3Q{pNr`I3EU^q3@t;KtK$@%n-G=h5< ze7JL;D++qx+0R36*eQgLH_ko4)k~rfHog?5ho7`8yzb?oG?DeVE;D??fp9_G5_{Cf zkVs9w+_>X(4%I~xwRiRORp>i+>4vUBZFpWlBqW0Bk6R`&ZX~KW1-96mtRm{kQ}cq+ zmWWCmYMGLY7*Q`1seb+A**I?Rae?$cLm4=LB3S=akv~>JQ!D?2Kl*cXik-!F7FFLaaJaB;qnP{D!<4@(0FAxNY=tHE}Up(6sL$1pc7J}d7_nx=%_Unm{ z*OHpCfjb>0`iDwvNFO^;cF%|I8_8t7>Sn!8%F^L39-G+Ul>`J5_R*7?A^Mah*+6G%>mL7)0C+dJjZo}xI>Hm4A1&OZWOOmeQNN{6EN$($Dek7nS z6E;IY5&^z^)_?lt|52Hp|GqL;CvHg&GNFxJ!uo`PdY+cPDbM1||X)cE+XW^V^@ zP)oqD94C2flRm?g`Z?QVtW7#5#3x-Yb!dl;g9Lo5qLIh)Km|M$b zhpo~%T+gU6G9q9wf5GIVO`%i37UE|$cGTYCwJ5@@K}||{Mzf}=eMwEX!t>}D4xFSg zP@v3Q`#J0Y7R#fgjFV`HxYIhEQ@#Dc26`B5VR#weg`B8{{WX?t8m&~-0QcpqL;xyV z3aW55^mF3chCJ_d^IlU4-T6GiOQdS>U{m2^&Q0`yw-=NR_Sd}8z6y&lrm(EjwN9K| zy{x=#o=PgXATc}qx+myYqF|Mw9e;9%g)Rz4V*BsJNdx)i(R?%a^eXA)UAA|Ee@zQz zK-K6;nNkY_2m%GD8~yK;hOO0gkDlowvn0dc4$BJr!gqO_ zq>db#0qj6!7^Q(5ndC<1C-1Jv2YD4p;U$j+2TQ?a(oX!1T@F>{7`KtzxlR$y44dL0 zymZOkp@wn8a$elK1!#JVH=y{%+M;~V&2 zy8jMt@fj^=LIB-K0lG8)A5y~q>cO93r488uCbXVQCAI{ua-7=e0dRexJ_BREPa4z3 z}7+9b-$Qf))6M(@o0OfKz>U z-82{b9r(Kh*y3s?lhV_O!?U4_0Brtk%C}b^MHeoYde3eHJ}H|vu*tRrX$xRF>ftI~ zUObi|T+nwg{0LHM0=sX1M)`s&rR`jI&(l2o@6oQ{dw*Omo&nSxn$5+t0Ei_B5bOVv zvj2ZiU5NgDo*Pb*u^nJS3V9R=DQN(2RZ%Mkj}W2-v!vBT(BBDACv#=XEq|plJEr#p zW>7`-H8wX0QD&su-=FMaG%C~V-ZQk}om65nHimJT@-X>otxVJ0tXvm0qJn*k{ix>o z{gD}a6)v8w4aY(>j6Z{)D1Rj817Rk-U(%0 zR)!f!Yg?a~UeQxlCI^R6wr4$kM88PGB#c8t9bOBYWIVftZjuelna{g1b_BvhzU5zk zWiU5$B2HrdwOcP_s)<$kN!Tim7d;OKm=EVYq7X4BC{#^eZ zv5cA!%1QqZ_O1i6p*K^(o+&_9(|;uEAHgsl`EP^au+leK5N5QEN7~FWjEWI-g$Q7F zVUeOM;F9i7Nf|Be-DE@=a*hVCV7}i6fHJm;aZx(W=jNR*xX*1We<&d=I#>_}Sc$i} zT4o25s~u*mnVy4Dz4EO&F=pC4#!c$9ybM=7$8sj1)5FY5F|uvF~@I~YelwM zX8I;F9*j!jX4b-uz$<5oTugXvP4JB!EMz3f!8NHc#c1NY-`O@5xC!-43(vHZ+St5s z{Ob1h91Y5tL5m>)aiHxHSCYov#jph{lckDZFO7~PP}G^xUZI#;spETh&rKj*y4=q| zy=*;Qlh?1)Se>mA%1Ap!yAv89R!v4#)U%-DD4B#T(PTnS0W#%b5)@r{fl#5JluZRL zG(3~oJU^xR3Nsm3nIJ9PGZ#hdaaki>rx%bg3I zIjl5m2|%mQv>Mn!r75l#P8P|8{`c|aGf3k}6~-nB3ER(B#ol+c42G}5EUK5-)SI*M->5aygRp>jTJ>tr6vkA4mBG{qTh3xr- zBK3i2o%eoOhZ+~8e3hB1@I>M^hpJ$P6YY2IWb88dC~O45)Rcyb5qOHTI=e)J-&ROX zZObt+j?zXccEx=3zMcsrGis#o9y=}4*$Kj-5=2ei@Zy9y#JZ@S`nE$4eU_`stFy=H zV~SE#-t!VVwT)=se`$M3$nyR$iIqiUK8k)dV-AwT!oe}+@o~d^QMaMxEM;M!EJO=q zog5*|@QI5IDMx1Kkk8G|KLYm1uHI8X=|yihUG zoS^>^$n+Gf0rgG=-@v`A%6CTI0~XSRj^?I(>`R^ra2Vrb?={3thBZv@UxKOu*h`QO zCu|H5)bAe&`bS*F`1`mzoU{QjDN@Mg3!2Oxz@?QjLU87W%4{Tv@~xoKbLZBp#5YMk zZkp|uT9BA`Z@?(m z7Gy7|A*Z1V2FEZw>-zc%Xmu0DRpn)BbiUMoh34psrHxWrvs{U4E!ScjKDrCytmU zz_dz^a15I&>+`w&ni~t8YDlK5UwRiX_#Rs1+j9e9#_}~!`7XG97x_BxlVb|8V3>V9 zmKpD)=;fkH=3IAnixTI!X~ZBFMkXpY6+Aj?zu`sl-%qizSu=3@)dWVy%y%{x%iLTF z^ZN|@BJBL}8zyZolLBU%pUevYihBGbMgPdV(P)4Bw#0DKq$DUa(kMVtV6SS0Ycfm3 zKq5~hK?41AS)2R~z%MXV(3uw&LJmij{}gEo{Bcs?jyxQe(Q(K8-PX#cnh>*}KUn4_ z@A<>jj56o{>E!-np$_9XfS*J(O|9ZIwGx%=MkZs7W9&P>nkM?OHJWM?(Ra$))H)L} z)WovNLQ20Q^uw=ODT|p=l(|Z!EEg5y(A;{SwX4ti@bKL3pSyp)uRYs)clUgr&-2kV zH+og1RcBnu{9+ljpk+|?L1VPax@z#}H~&RR9p7twjS4(Rg50u4qyv$z?e~o5n#$K1 z-VNUndfX&8e~YN}noOQ(FD`nR-d5e?A)IJw2tNHI)cQbJ{IE+~u*yTTI(43--L^uh zy4xYEDK_)tj?If4VtbQZk=QA|d&Zlql%C zX{T}-{F%!2-v}G{a6B>R-@^wK6(oOCJbdQ)IRvI1_Y4@%`hc)##0~!~3KE-6{$SX6 z<^Z(!k|_k7iw@WR(;+sUK)|r^ZD(jlE#$YgzsSU(>5w5S!k@ zWZ3u`7_A^Sy z#wS}4e(F3Mc&c-*nPZI)aG)IK!Y{*U)8ib*vhjHZ#Cu(Jz(Ks}=>-GFrFn=ScGCd| z5vPTE298V95Fd8e0S7Usg=z+l%dikn@z4PWQKv;&299fi5KofN9_RY{##JrI+In-c zFyyqx#aJ*dyg*sv!z+V?(=rT0#^nJhzw+gkLA+_PfFa|3ER-(`yfVl(-PJN=yx)Ry dq93mef=zc{NH#E}f7va_tBc%*PYs}-egl6@-&p_v diff --git a/config/alfresco/cache-context.xml b/config/alfresco/cache-context.xml index 36331fc2da..314a9f26b6 100644 --- a/config/alfresco/cache-context.xml +++ b/config/alfresco/cache-context.xml @@ -594,6 +594,40 @@ + + + + + + + + + + + + + + + org.alfresco.cache.readersDeniedCache + + + + + + + + + + + + + org.alfresco.readersDeniedTransactionalCache + + + + + + diff --git a/config/alfresco/content-services-context.xml b/config/alfresco/content-services-context.xml index 4d8a38b340..985b516203 100644 --- a/config/alfresco/content-services-context.xml +++ b/config/alfresco/content-services-context.xml @@ -153,6 +153,7 @@ classpath:alfresco/mimetype/mimetype-map.xml classpath:alfresco/mimetype/mimetype-map-openoffice.xml + classpath*:alfresco/module/*/mimetype-map*.xml classpath*:alfresco/extension/mimetype/*-map.xml diff --git a/config/alfresco/dao/dao-context.xml b/config/alfresco/dao/dao-context.xml index 87c53182ca..8e7b01d457 100644 --- a/config/alfresco/dao/dao-context.xml +++ b/config/alfresco/dao/dao-context.xml @@ -285,6 +285,7 @@ + diff --git a/config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/Schema-Reference-ACT.xml b/config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/Schema-Reference-ACT.xml index 3c662ea50c..4567af8875 100644 --- a/config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/Schema-Reference-ACT.xml +++ b/config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/Schema-Reference-ACT.xml @@ -1034,6 +1034,11 @@ BYTEARRAY_ID_ + + + TASK_ID_ + + @@ -1218,6 +1223,11 @@ NAME_ + + + TASK_ID_ + +
diff --git a/config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/Schema-Reference-AVM.xml b/config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/Schema-Reference-AVM.xml index 893b0908ec..ef8d5020b6 100644 --- a/config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/Schema-Reference-AVM.xml +++ b/config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/Schema-Reference-AVM.xml @@ -60,12 +60,17 @@ falsefalse - + varchar(160) false false - + + varchar(160) + false + false + + bigint false false @@ -73,7 +78,7 @@ - name + lc_name parent_id @@ -100,6 +105,12 @@ parent_id + + + lc_name + parent_id + +
diff --git a/config/alfresco/dbscripts/create/org.hibernate.dialect.PostgreSQLDialect/Schema-Reference-ACT.xml b/config/alfresco/dbscripts/create/org.hibernate.dialect.PostgreSQLDialect/Schema-Reference-ACT.xml index c58389858a..3a30395880 100644 --- a/config/alfresco/dbscripts/create/org.hibernate.dialect.PostgreSQLDialect/Schema-Reference-ACT.xml +++ b/config/alfresco/dbscripts/create/org.hibernate.dialect.PostgreSQLDialect/Schema-Reference-ACT.xml @@ -449,7 +449,7 @@ - + proc_def_id_ business_key_ @@ -898,7 +898,7 @@ - + proc_def_id_ business_key_ diff --git a/config/alfresco/dbscripts/create/org.hibernate.dialect.PostgreSQLDialect/Schema-Reference-AVM.xml b/config/alfresco/dbscripts/create/org.hibernate.dialect.PostgreSQLDialect/Schema-Reference-AVM.xml index 4f879ff503..4434aca4e0 100644 --- a/config/alfresco/dbscripts/create/org.hibernate.dialect.PostgreSQLDialect/Schema-Reference-AVM.xml +++ b/config/alfresco/dbscripts/create/org.hibernate.dialect.PostgreSQLDialect/Schema-Reference-AVM.xml @@ -64,12 +64,17 @@ false false - + varchar(160) false false - + + varchar(160) + false + false + + int8 false false @@ -78,7 +83,7 @@ parent_id - name + lc_name @@ -104,6 +109,12 @@ parent_id + + + lc_name + parent_id + +
@@ -708,7 +719,7 @@ - + version_id avm_store_id diff --git a/config/alfresco/dbscripts/upgrade/4.0/org.hibernate.dialect.Dialect/ActivitiTaskIdIndexes.sql b/config/alfresco/dbscripts/upgrade/4.0/org.hibernate.dialect.Dialect/ActivitiTaskIdIndexes.sql new file mode 100644 index 0000000000..b77b6fe491 --- /dev/null +++ b/config/alfresco/dbscripts/upgrade/4.0/org.hibernate.dialect.Dialect/ActivitiTaskIdIndexes.sql @@ -0,0 +1,26 @@ +-- +-- Title: Add missing Activiti indexes on task-id for runtime and history variables. +-- Database: Generic +-- Since: V4.0 Schema 5029 +-- Author: Frederik Heremans +-- +-- Please contact support@alfresco.com if you need assistance with the upgrade. +-- + +-- Add index to runtime variable table +create index ACT_IDX_VARIABLE_TASK_ID on ACT_RU_VARIABLE(TASK_ID_); + +-- Add index to history variable table +create index ACT_IDX_HI_DETAIL_TASK_ID on ACT_HI_DETAIL(TASK_ID_); + +-- +-- Record script finish +-- +DELETE FROM alf_applied_patch WHERE id = 'patch.db-V4.0-Activiti-task-id-indexes'; +INSERT INTO alf_applied_patch + (id, description, fixes_from_schema, fixes_to_schema, applied_to_schema, target_schema, applied_on_date, applied_to_server, was_executed, succeeded, report) + VALUES + ( + 'patch.db-V4.0-Activiti-task-id-indexes', 'Manually executed script upgrade V4.0: Add missing Activiti indexes on task-id', + 0, 6003, -1, 6004, null, 'UNKNOWN', ${TRUE}, ${TRUE}, 'Script completed' + ); \ No newline at end of file diff --git a/config/alfresco/dbscripts/upgrade/4.0/org.hibernate.dialect.MySQLInnoDBDialect/ActivitiTaskIdIndexes.sql b/config/alfresco/dbscripts/upgrade/4.0/org.hibernate.dialect.MySQLInnoDBDialect/ActivitiTaskIdIndexes.sql new file mode 100644 index 0000000000..d9ead0f12a --- /dev/null +++ b/config/alfresco/dbscripts/upgrade/4.0/org.hibernate.dialect.MySQLInnoDBDialect/ActivitiTaskIdIndexes.sql @@ -0,0 +1,26 @@ +-- +-- Title: Add missing Activiti indexes on task-id for runtime and history variables. +-- Database: MySQL +-- Since: V4.0 Schema 5029 +-- Author: Frederik Heremans +-- +-- Please contact support@alfresco.com if you need assistance with the upgrade. +-- + +-- Add index to runtime variable table +create index ACT_IDX_VARIABLE_TASK_ID on ACT_RU_VARIABLE(TASK_ID_); + +-- Add index to history variable table +create index ACT_IDX_HI_DETAIL_TASK_ID on ACT_HI_DETAIL(TASK_ID_); + +-- +-- Record script finish +-- +DELETE FROM alf_applied_patch WHERE id = 'patch.db-V4.0-Activiti-task-id-indexes'; +INSERT INTO alf_applied_patch + (id, description, fixes_from_schema, fixes_to_schema, applied_to_schema, target_schema, applied_on_date, applied_to_server, was_executed, succeeded, report) + VALUES + ( + 'patch.db-V4.0-Activiti-task-id-indexes', 'Manually executed script upgrade V4.0: Add missing Activiti indexes on task-id', + 0, 6003, -1, 6004, null, 'UNKNOWN', ${TRUE}, ${TRUE}, 'Script completed' + ); \ No newline at end of file diff --git a/config/alfresco/dbscripts/upgrade/4.0/org.hibernate.dialect.PostgreSQLDialect/ActivitiTaskIdIndexes.sql b/config/alfresco/dbscripts/upgrade/4.0/org.hibernate.dialect.PostgreSQLDialect/ActivitiTaskIdIndexes.sql new file mode 100644 index 0000000000..beae52260b --- /dev/null +++ b/config/alfresco/dbscripts/upgrade/4.0/org.hibernate.dialect.PostgreSQLDialect/ActivitiTaskIdIndexes.sql @@ -0,0 +1,26 @@ +-- +-- Title: Add missing Activiti indexes on task-id for runtime and history variables. +-- Database: PostgreSQL +-- Since: V4.0 Schema 5029 +-- Author: Frederik Heremans +-- +-- Please contact support@alfresco.com if you need assistance with the upgrade. +-- + +-- Add index to runtime variable table +create index ACT_IDX_VARIABLE_TASK_ID on ACT_RU_VARIABLE(TASK_ID_); --(optional) + +-- Add index to history variable table +create index ACT_IDX_HI_DETAIL_TASK_ID on ACT_HI_DETAIL(TASK_ID_); --(optional) + +-- +-- Record script finish +-- +DELETE FROM alf_applied_patch WHERE id = 'patch.db-V4.0-Activiti-task-id-indexes'; +INSERT INTO alf_applied_patch + (id, description, fixes_from_schema, fixes_to_schema, applied_to_schema, target_schema, applied_on_date, applied_to_server, was_executed, succeeded, report) + VALUES + ( + 'patch.db-V4.0-Activiti-task-id-indexes', 'Manually executed script upgrade V4.0: Add missing Activiti indexes on task-id', + 0, 6003, -1, 6004, null, 'UNKNOWN', ${TRUE}, ${TRUE}, 'Script completed' + ); \ No newline at end of file diff --git a/config/alfresco/ehcache-default.xml b/config/alfresco/ehcache-default.xml index 7ba8664b29..a763850091 100644 --- a/config/alfresco/ehcache-default.xml +++ b/config/alfresco/ehcache-default.xml @@ -209,6 +209,13 @@ eternal="true" overflowToDisk="false" /> + + + + delete from + alf_transaction txn + where not exists + ( + select 1 + from + alf_node node + where + node.transaction_id = txn.id + ) + = #{minCommitTime}]]> + + + + + + + + + \ No newline at end of file diff --git a/config/alfresco/ibatis/org.hibernate.dialect.Dialect/permissions-common-SqlMap.xml b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/permissions-common-SqlMap.xml index ad07c5e162..e83c730e6c 100644 --- a/config/alfresco/ibatis/org.hibernate.dialect.Dialect/permissions-common-SqlMap.xml +++ b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/permissions-common-SqlMap.xml @@ -95,6 +95,10 @@ + + + + @@ -493,6 +497,26 @@ authority = ? + + + + diff --git a/config/alfresco/messages/action-config_fr.properties b/config/alfresco/messages/action-config_fr.properties index c84fd59481..cb19b659c2 100755 --- a/config/alfresco/messages/action-config_fr.properties +++ b/config/alfresco/messages/action-config_fr.properties @@ -156,7 +156,7 @@ export.title=Exporter l'espace export.description=Exporte un espace et, facultativement, ses enfants dans un progiciel d'exportation Alfresco. export.package.description=Package de contenu Alfresco pour l''espace ''{0}''. export.root.package.description=Package de contenu Alfresco pour l'entrep\u00f4t complet. -export.store.package.description=Export du magasin ''{0}'' de l''entrep\u00f4t Alfresco. +export.store.package.description=Export du store ''{0}'' de l''entrep\u00f4t Alfresco. export.generic.package.description=Export de l'entrep\u00f4t Alfresco. export.package.error=Fichier temporaire d'export non trouv\u00e9 @@ -187,26 +187,26 @@ simple-avm-submit.description=Transf\u00e8re tout nouveau noeud de l'\u00e9l\u00 simple-avm-promote.title=Simple transfert vers un bac \u00e0 sable simple-avm-promote.description=Transf\u00e8re tout nouveau noeud de l'\u00e9l\u00e9ment dans le bac \u00e0 sable s\u00e9lectionn\u00e9. -simple-avm-promote.target-store.display-label=Nom du magasin AVM cible. +simple-avm-promote.target-store.display-label=Nom du store AVM cible. -avm-revert-store.title=R\u00e9tablir un noeud unique dans un magasin. +avm-revert-store.title=R\u00e9tablir un noeud unique dans un store. avm-revert-store.description=R\u00e9tablit une version pr\u00e9c\u00e9dente du noeud indiqu\u00e9 et des noeuds inf\u00e9rieurs. avm-revert-store.version.display-label=Version \u00e0 r\u00e9tablir. -avm-revert-list.title=R\u00e9tablir une liste de noeuds dans un magasin. +avm-revert-list.title=R\u00e9tablir une liste de noeuds dans un store. avm-revert-list.description=R\u00e9tablit tous les noeuds de la liste. avm-revert-list.version.display-label=Version \u00e0 r\u00e9tablir. avm-revert-list.node-list.display-label=Cha\u00eene encod\u00e9e de la liste des noeuds \u00e0 r\u00e9tablir. avm-revert-list.flatten.display-label=S'il faut finaliser un environnement de recette apr\u00e8s le r\u00e9tablissement. -avm-revert-list.store.display-label=Nom du magasin r\u00e9tabli, uniquement n\u00e9cessaire pour la finalisation. -avm-revert-list.staging.display-label=Nom du magasin de recette pour la finalisation. -avm-revert-list.flatten-path.display-label=Chemin relatif du magasin qui doit \u00eatre finalis\u00e9. +avm-revert-list.store.display-label=Nom du store r\u00e9tabli, uniquement n\u00e9cessaire pour la finalisation. +avm-revert-list.staging.display-label=Nom du store de recette pour la finalisation. +avm-revert-list.flatten-path.display-label=Chemin relatif du store qui doit \u00eatre finalis\u00e9. avm-revert-to-version.title=R\u00e9tablir une version particuli\u00e8re d'un noeud. avm-revert-to-version.description=R\u00e9tablit une version donn\u00e9e d'un noeud. avm-revert-to-version.to-revert.display-label=Descripteur de noeud AVM de la version \u00e0 r\u00e9tablir. -avm-undo-list.title=Rendre une liste de noeuds dans un magasin transparente pour la recette. +avm-undo-list.title=Rendre une liste de noeuds dans un store transparente pour la recette. avm-undo-list.description=Retour arri\u00e8re dans le bac \u00e0 sable utilisateur. avm-undo-list.node-list.display-label=Cha\u00eene encod\u00e9e de la liste des noeuds \u00e0 r\u00e9tablir. diff --git a/config/alfresco/messages/bpm-messages_de.properties b/config/alfresco/messages/bpm-messages_de.properties index 9643358e2e..3f480292e8 100755 --- a/config/alfresco/messages/bpm-messages_de.properties +++ b/config/alfresco/messages/bpm-messages_de.properties @@ -1,13 +1,13 @@ -#Display labels for base Business Process Model +# Display labels for base Business Process Model bpm_businessprocessmodel.title=Modell f\u00fcr Gesch\u00e4ftsprozess bpm_businessprocessmodel.description=Grundlegende Definitionen aller Gesch\u00e4ftsprozesse -#Default transition +# Default transition bpm_businessprocessmodel.transition.title=Aufgabe erledigt bpm_businessprocessmodel.transition.description=Aufgabe erledigt -#Base Task +# Base Task bpm_businessprocessmodel.type.bpm_task.title=Aufgabe bpm_businessprocessmodel.type.bpm_task.description=Aufgabe bpm_businessprocessmodel.property.bpm_taskId.title=Identifikator @@ -29,7 +29,7 @@ bpm_businessprocessmodel.property.bpm_comment.description=Kommentar bpm_businessprocessmodel.association.bpm_pooledActors.title=Geb\u00fcndelte Benutzer bpm_businessprocessmodel.association.bpm_pooledActors.description=Pool -#Workflow Task +# Workflow Task bpm_businessprocessmodel.type.bpm_workflowTask.title=Aufgabe im Workflow bpm_businessprocessmodel.type.bpm_workflowTask.description=Vor einem Workflow zugewiesene Aufgabe bpm_businessprocessmodel.property.bpm_workflowDefinitionId.title=Definitions-ID des Workflows @@ -76,11 +76,11 @@ bpm_businessprocessmodel.association.bpm_groupAssignee.description=Bevollm\u00e4 bpm_businessprocessmodel.association.bpm_groupAssignees.title=Bevollm\u00e4chtigte f\u00fcr die Gruppe der Workflows bpm_businessprocessmodel.association.bpm_groupAssignees.description=Bevollm\u00e4chtigte f\u00fcr die Gruppe der Workflows -#Error Messages +# Error Messages workflow.get.task.definition.metadata.error=Aufgabentypdefinition {0} konnte nicht gefunden werden. workflow.package.already.associated.error=Dieser Node wird bereits als Workflow-Paket verwendet. NodeRef: {0} -#List constraint display labels +# List constraint display labels listconstraint.bpm_allowedPriority.1=Hoch listconstraint.bpm_allowedPriority.2=Mittel listconstraint.bpm_allowedPriority.3=Niedrig diff --git a/config/alfresco/messages/bpm-messages_es.properties b/config/alfresco/messages/bpm-messages_es.properties index a4fd57d01d..1e93c89844 100755 --- a/config/alfresco/messages/bpm-messages_es.properties +++ b/config/alfresco/messages/bpm-messages_es.properties @@ -1,13 +1,13 @@ -#Display labels for base Business Process Model +# Display labels for base Business Process Model bpm_businessprocessmodel.title=Modelo de procesos empresariales bpm_businessprocessmodel.description=Definiciones b\u00e1sicas de todos los procesos empresariales -#Default transition +# Default transition bpm_businessprocessmodel.transition.title=Tarea hecha bpm_businessprocessmodel.transition.description=Tarea hecha -#Base Task +# Base Task bpm_businessprocessmodel.type.bpm_task.title=Tarea bpm_businessprocessmodel.type.bpm_task.description=Tarea bpm_businessprocessmodel.property.bpm_taskId.title=Identificador @@ -29,7 +29,7 @@ bpm_businessprocessmodel.property.bpm_comment.description=Comentario bpm_businessprocessmodel.association.bpm_pooledActors.title=Usuarios agrupados bpm_businessprocessmodel.association.bpm_pooledActors.description=Grupo -#Workflow Task +# Workflow Task bpm_businessprocessmodel.type.bpm_workflowTask.title=Tarea de flujo de trabajo bpm_businessprocessmodel.type.bpm_workflowTask.description=Tarea asignada por un flujo de trabajo bpm_businessprocessmodel.property.bpm_workflowDefinitionId.title=Id de definici\u00f3n de flujo de trabajo @@ -76,11 +76,11 @@ bpm_businessprocessmodel.association.bpm_groupAssignee.description=Usuario a asi bpm_businessprocessmodel.association.bpm_groupAssignees.title=Usuarios a asignar la tarea del grupo de flujo de trabajo bpm_businessprocessmodel.association.bpm_groupAssignees.description=Usuarios a asignar la tarea del grupo de flujo de trabajo -#Error Messages +# Error Messages workflow.get.task.definition.metadata.error=No se pudo encontrar la definici\u00f3n de tipo de tarea {0}. workflow.package.already.associated.error=Este nodo ya se est\u00e1 utilizando como paquete de flujo de trabajo. NodeRef: {0} -#List constraint display labels +# List constraint display labels listconstraint.bpm_allowedPriority.1=Alta listconstraint.bpm_allowedPriority.2=Media listconstraint.bpm_allowedPriority.3=Baja diff --git a/config/alfresco/messages/bpm-messages_fr.properties b/config/alfresco/messages/bpm-messages_fr.properties index 00564a963a..787858e39c 100755 --- a/config/alfresco/messages/bpm-messages_fr.properties +++ b/config/alfresco/messages/bpm-messages_fr.properties @@ -1,13 +1,13 @@ -#Display labels for base Business Process Model +# Display labels for base Business Process Model bpm_businessprocessmodel.title=Mod\u00e8le de Processus M\u00e9tier bpm_businessprocessmodel.description=D\u00e9finitions de base de tous les Processus M\u00e9tier -#Default transition +# Default transition bpm_businessprocessmodel.transition.title=T\u00e2che termin\u00e9e bpm_businessprocessmodel.transition.description=T\u00e2che termin\u00e9e -#Base Task +# Base Task bpm_businessprocessmodel.type.bpm_task.title=T\u00e2che bpm_businessprocessmodel.type.bpm_task.description=T\u00e2che bpm_businessprocessmodel.property.bpm_taskId.title=Identifiant @@ -29,7 +29,7 @@ bpm_businessprocessmodel.property.bpm_comment.description=Commentaire bpm_businessprocessmodel.association.bpm_pooledActors.title=Liste bpm_businessprocessmodel.association.bpm_pooledActors.description=Liste -#Workflow Task +# Workflow Task bpm_businessprocessmodel.type.bpm_workflowTask.title=T\u00e2che du workflow bpm_businessprocessmodel.type.bpm_workflowTask.description=T\u00e2che assign\u00e9e par un Workflow bpm_businessprocessmodel.property.bpm_workflowDefinitionId.title=Identifiant de la d\u00e9finition du Workflow @@ -57,7 +57,6 @@ bpm_businessprocessmodel.aspect.bpm_workflowPackage.description=La collection du bpm_businessprocessmodel.type.bpm_activitiStartTask.title=T\u00e2che de d\u00e9marrage de workflow bpm_businessprocessmodel.type.bpm_activitiStartTask.description=T\u00e2che de r\u00e9colte des informations n\u00e9cessaires au d\u00e9marrage du workflow -#Workflow Start Task bpm_businessprocessmodel.type.bpm_startTask.title=T\u00e2che de D\u00e9marrage du Workflow bpm_businessprocessmodel.type.bpm_startTask.description=T\u00e2che utilis\u00e9e pour collecter des informations n\u00e9cessaires pour d\u00e9marrer le Workflow bpm_businessprocessmodel.property.bpm_workflowDescription.title=Description @@ -77,11 +76,11 @@ bpm_businessprocessmodel.association.bpm_groupAssignee.description=Propri\u00e9t bpm_businessprocessmodel.association.bpm_groupAssignees.title=Propri\u00e9taires du Groupe de Workflow bpm_businessprocessmodel.association.bpm_groupAssignees.description=Propri\u00e9taires du Groupe de Workflow -#Error Messages +# Error Messages workflow.get.task.definition.metadata.error=Impossible de trouver la d\u00e9finition de type de t\u00e2che {0}. workflow.package.already.associated.error=Ce n\u0153ud est d\u00e9j\u00e0 utilis\u00e9 comme paquetage de workflow. NodeRef : {0} -#List constraint display labels +# List constraint display labels listconstraint.bpm_allowedPriority.1=\u00c9lev\u00e9e listconstraint.bpm_allowedPriority.2=Moyenne listconstraint.bpm_allowedPriority.3=Basse diff --git a/config/alfresco/messages/bpm-messages_it.properties b/config/alfresco/messages/bpm-messages_it.properties index 9d39950619..6d7d3adbfb 100755 --- a/config/alfresco/messages/bpm-messages_it.properties +++ b/config/alfresco/messages/bpm-messages_it.properties @@ -1,13 +1,13 @@ -#Display labels for base Business Process Model +# Display labels for base Business Process Model bpm_businessprocessmodel.title=Modello di processo aziendale bpm_businessprocessmodel.description=Definizioni di base di tutti i processi aziendali -#Default transition +# Default transition bpm_businessprocessmodel.transition.title=Compito eseguito bpm_businessprocessmodel.transition.description=Compito eseguito -#Base Task +# Base Task bpm_businessprocessmodel.type.bpm_task.title=Compito bpm_businessprocessmodel.type.bpm_task.description=Compito bpm_businessprocessmodel.property.bpm_taskId.title=Identificativo @@ -29,7 +29,7 @@ bpm_businessprocessmodel.property.bpm_comment.description=Commento bpm_businessprocessmodel.association.bpm_pooledActors.title=Utenti nel pool bpm_businessprocessmodel.association.bpm_pooledActors.description=Pool -#Workflow Task +# Workflow Task bpm_businessprocessmodel.type.bpm_workflowTask.title=Compito del workflow bpm_businessprocessmodel.type.bpm_workflowTask.description=Compito assegnato da un workflow bpm_businessprocessmodel.property.bpm_workflowDefinitionId.title=ID di definizione del workflow @@ -76,11 +76,11 @@ bpm_businessprocessmodel.association.bpm_groupAssignee.description=Assegnatario bpm_businessprocessmodel.association.bpm_groupAssignees.title=Assegnatari del gruppo del workflow bpm_businessprocessmodel.association.bpm_groupAssignees.description=Assegnatari del gruppo del workflow -#Error Messages +# Error Messages workflow.get.task.definition.metadata.error=Impossibile trovare la definizione del tipo di compito {0}. workflow.package.already.associated.error=Questo nodo \u00e8 gi\u00e0 utilizzato come pacchetto di workflow! NodeRef: {0} -#List constraint display labels +# List constraint display labels listconstraint.bpm_allowedPriority.1=Alta listconstraint.bpm_allowedPriority.2=Media listconstraint.bpm_allowedPriority.3=Bassa diff --git a/config/alfresco/messages/bpm-messages_nl.properties b/config/alfresco/messages/bpm-messages_nl.properties index 97350d19fe..73b9ae891e 100755 --- a/config/alfresco/messages/bpm-messages_nl.properties +++ b/config/alfresco/messages/bpm-messages_nl.properties @@ -57,7 +57,6 @@ bpm_businessprocessmodel.aspect.bpm_workflowPackage.description=De verzameling c bpm_businessprocessmodel.type.bpm_activitiStartTask.title=Begintaak van de Werkstroom bpm_businessprocessmodel.type.bpm_activitiStartTask.description=Taak die wordt gebruikt om informatie te verzamelen om de werkstroom te initi\u00ebren -# Workflow Start Task bpm_businessprocessmodel.type.bpm_startTask.title=Begintaak van werkstroom bpm_businessprocessmodel.type.bpm_startTask.description=Taak die wordt gebruikt om informatie te verzamelen die nodig is om de werkstroom te starten bpm_businessprocessmodel.property.bpm_workflowDescription.title=Beschrijving diff --git a/config/alfresco/messages/content-model_de.properties b/config/alfresco/messages/content-model_de.properties index 8a2f079bc4..145bd2b4de 100755 --- a/config/alfresco/messages/content-model_de.properties +++ b/config/alfresco/messages/content-model_de.properties @@ -358,3 +358,6 @@ cm_contentmodel.property.cm_isIndexed.title=wurde in den Index aufgenommen cm_contentmodel.property.cm_isIndexed.description=ist der Node, der in den Index aufgenommen wurde und \u00fcber die Suche gefunden werden kann. cm_contentmodel.property.cm_isContentIndexed.title=ist Inhalt, der in den Index aufgenommen wurde cm_contentmodel.property.cm_isContentIndexed.description=Sind die d:content Eigenschaften des Nodes in den Index aufgenommen worden? + +cm_contentmodel.property.cm_tagScopeSummary.title=Tag Summary +cm_contentmodel.property.cm_tagScopeSummary.description=Tag Summary \ No newline at end of file diff --git a/config/alfresco/messages/content-model_es.properties b/config/alfresco/messages/content-model_es.properties index 4d68731499..207f618565 100755 --- a/config/alfresco/messages/content-model_es.properties +++ b/config/alfresco/messages/content-model_es.properties @@ -358,3 +358,6 @@ cm_contentmodel.property.cm_isIndexed.title=Indexar propiedades cm_contentmodel.property.cm_isIndexed.description=Es el nodo indexado y se puede encontrar mediante una b\u00fasqueda. cm_contentmodel.property.cm_isContentIndexed.title=Indexar contenido cm_contentmodel.property.cm_isContentIndexed.description=\u00bfEst\u00e1n las propiedades del nodo d:content indexadas? + +cm_contentmodel.property.cm_tagScopeSummary.title=Tag Summary +cm_contentmodel.property.cm_tagScopeSummary.description=Tag Summary \ No newline at end of file diff --git a/config/alfresco/messages/content-model_fr.properties b/config/alfresco/messages/content-model_fr.properties index c396771386..d84e80e7b0 100755 --- a/config/alfresco/messages/content-model_fr.properties +++ b/config/alfresco/messages/content-model_fr.properties @@ -358,3 +358,6 @@ cm_contentmodel.property.cm_isIndexed.title=Est index\u00e9 cm_contentmodel.property.cm_isIndexed.description=N\u0153ud index\u00e9 et pouvant \u00eatre faire l'objet d'une recherche. cm_contentmodel.property.cm_isContentIndexed.title=Est index\u00e9 selon le contenu cm_contentmodel.property.cm_isContentIndexed.description=Les propri\u00e9t\u00e9s d:content du n\u0153ud sont-elles index\u00e9es ? + +cm_contentmodel.property.cm_tagScopeSummary.title=Tag Summary +cm_contentmodel.property.cm_tagScopeSummary.description=Tag Summary \ No newline at end of file diff --git a/config/alfresco/messages/content-model_it.properties b/config/alfresco/messages/content-model_it.properties index b6898cd75b..da52f85316 100755 --- a/config/alfresco/messages/content-model_it.properties +++ b/config/alfresco/messages/content-model_it.properties @@ -358,3 +358,6 @@ cm_contentmodel.property.cm_isIndexed.title=Indicizzato cm_contentmodel.property.cm_isIndexed.description=Il nodo \u00e8 indicizzato e pu\u00f2 essere trovato tramite ricerca. cm_contentmodel.property.cm_isContentIndexed.title=Contenuto indicizzato cm_contentmodel.property.cm_isContentIndexed.description=Le propriet\u00e0 d:content del nodo sono indicizzate? + +cm_contentmodel.property.cm_tagScopeSummary.title=Tag Summary +cm_contentmodel.property.cm_tagScopeSummary.description=Tag Summary \ No newline at end of file diff --git a/config/alfresco/messages/content-model_ja.properties b/config/alfresco/messages/content-model_ja.properties index 4dd013b793..d31e31b9e8 100755 --- a/config/alfresco/messages/content-model_ja.properties +++ b/config/alfresco/messages/content-model_ja.properties @@ -358,3 +358,6 @@ cm_contentmodel.property.cm_isIndexed.title=\u5c5e\u6027\u5024\u30a4\u30f3\u30c7 cm_contentmodel.property.cm_isIndexed.description=\u691c\u7d22\u3067\u304d\u308b\u3088\u3046\u306b\u3001\u30ce\u30fc\u30c9\u306b\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u3092\u4ed8\u3051\u307e\u3059\u3002 cm_contentmodel.property.cm_isContentIndexed.title=\u30b3\u30f3\u30c6\u30f3\u30c4\u5168\u6587\u30a4\u30f3\u30c7\u30af\u30b7\u30f3\u30b0\u5bfe\u8c61 cm_contentmodel.property.cm_isContentIndexed.description=\u30ce\u30fc\u30c9\u306ed:content\u30d7\u30ed\u30d1\u30c6\u30a3\u306b\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u306f\u4ed8\u3051\u3089\u308c\u3066\u3044\u307e\u3059\u304b\uff1f + +cm_contentmodel.property.cm_tagScopeSummary.title=Tag Summary +cm_contentmodel.property.cm_tagScopeSummary.description=Tag Summary \ No newline at end of file diff --git a/config/alfresco/messages/content-model_nl.properties b/config/alfresco/messages/content-model_nl.properties index f871b2ebda..90a1c379ad 100755 --- a/config/alfresco/messages/content-model_nl.properties +++ b/config/alfresco/messages/content-model_nl.properties @@ -358,3 +358,6 @@ cm_contentmodel.property.cm_isIndexed.title=Is ge\u00efndexeerd cm_contentmodel.property.cm_isIndexed.description=De node is ge\u00efndexeerd en kan via de zoekfunctie worden gevonden. cm_contentmodel.property.cm_isContentIndexed.title=Is op content ge\u00efndexeerd cm_contentmodel.property.cm_isContentIndexed.description=De d:content-eigenschappen van de node zijn ge\u00efndexeerd. + +cm_contentmodel.property.cm_tagScopeSummary.title=Tag Summary +cm_contentmodel.property.cm_tagScopeSummary.description=Tag Summary \ No newline at end of file diff --git a/config/alfresco/messages/patch-service.properties b/config/alfresco/messages/patch-service.properties index babc3b866a..0c0c063e22 100644 --- a/config/alfresco/messages/patch-service.properties +++ b/config/alfresco/messages/patch-service.properties @@ -425,6 +425,8 @@ patch.activitiesTemplatesUpdate.description=Updates activities email templates. patch.activitiesTemplatesUpdate.error=Error retrieving base template when trying to patch the activity email templates. patch.activitiesTemplatesUpdate.result=Updated activities email templates. +patch.activitiesEmailTemplate.description=Creates activities email templates. + patch.avmToAdmRemoteStore.description=Migrates Share Surf config from AVM sitestore to DM Sites folder. patch.avmToAdmRemoteStore.complete=Completed Share Surf config migration. diff --git a/config/alfresco/messages/patch-service_de.properties b/config/alfresco/messages/patch-service_de.properties index e367a84b2f..4bcb96b00a 100755 --- a/config/alfresco/messages/patch-service_de.properties +++ b/config/alfresco/messages/patch-service_de.properties @@ -152,6 +152,7 @@ patch.siteLoadPatch.description=Loads a sample site into the repository. patch.siteLoadPatch.exists=The Site {0} already exists and so could not be imported patch.siteLoadPatch.noBootstrapViews=No bootstrap views were given for importing Site {0} - please check the bootstrap extension bean configuration patch.siteLoadPatch.result=Site {0} imported. +patch.siteLoadPatch.siteNotCreated=The site {0} is only created for new installs. patch.wcmFolders.description=Ensures the existance of the WCM specific 'Web Projects' and 'Web Forms' folders. patch.wcmFolders.webprojects.result.exists=The Web Projects folder already exists: {0} @@ -455,3 +456,8 @@ patch.fixBpmPackages.invalidBootsrapStore=Bootstrap store has not been set patch.fixBpmPackages.emptyContainer={0} node has no children patch.alterJBPM331CLOBcolumnsToNvarchar.description=Altering CLOB columns in the jBPM 3.3.1 tables to introduce Unicode characters support for jBPM 3.3.1 + +patch.avmIndexChildEntriesLower.description=Add an indexed column to avm_child_entries that allows case-insensitive querying of AVM files by name + +patch.imapUnsubscribedAspect.description=Patch to remove deprecated "imap:nonSubscribed" aspect from folders. +patch.imapUnsubscribedAspect.result.removed="imap:nonSubscribed" aspect was successfully removed from unsubscribed folders. diff --git a/config/alfresco/messages/patch-service_es.properties b/config/alfresco/messages/patch-service_es.properties index e367a84b2f..4bcb96b00a 100755 --- a/config/alfresco/messages/patch-service_es.properties +++ b/config/alfresco/messages/patch-service_es.properties @@ -152,6 +152,7 @@ patch.siteLoadPatch.description=Loads a sample site into the repository. patch.siteLoadPatch.exists=The Site {0} already exists and so could not be imported patch.siteLoadPatch.noBootstrapViews=No bootstrap views were given for importing Site {0} - please check the bootstrap extension bean configuration patch.siteLoadPatch.result=Site {0} imported. +patch.siteLoadPatch.siteNotCreated=The site {0} is only created for new installs. patch.wcmFolders.description=Ensures the existance of the WCM specific 'Web Projects' and 'Web Forms' folders. patch.wcmFolders.webprojects.result.exists=The Web Projects folder already exists: {0} @@ -455,3 +456,8 @@ patch.fixBpmPackages.invalidBootsrapStore=Bootstrap store has not been set patch.fixBpmPackages.emptyContainer={0} node has no children patch.alterJBPM331CLOBcolumnsToNvarchar.description=Altering CLOB columns in the jBPM 3.3.1 tables to introduce Unicode characters support for jBPM 3.3.1 + +patch.avmIndexChildEntriesLower.description=Add an indexed column to avm_child_entries that allows case-insensitive querying of AVM files by name + +patch.imapUnsubscribedAspect.description=Patch to remove deprecated "imap:nonSubscribed" aspect from folders. +patch.imapUnsubscribedAspect.result.removed="imap:nonSubscribed" aspect was successfully removed from unsubscribed folders. diff --git a/config/alfresco/messages/patch-service_fr.properties b/config/alfresco/messages/patch-service_fr.properties index e367a84b2f..4bcb96b00a 100755 --- a/config/alfresco/messages/patch-service_fr.properties +++ b/config/alfresco/messages/patch-service_fr.properties @@ -152,6 +152,7 @@ patch.siteLoadPatch.description=Loads a sample site into the repository. patch.siteLoadPatch.exists=The Site {0} already exists and so could not be imported patch.siteLoadPatch.noBootstrapViews=No bootstrap views were given for importing Site {0} - please check the bootstrap extension bean configuration patch.siteLoadPatch.result=Site {0} imported. +patch.siteLoadPatch.siteNotCreated=The site {0} is only created for new installs. patch.wcmFolders.description=Ensures the existance of the WCM specific 'Web Projects' and 'Web Forms' folders. patch.wcmFolders.webprojects.result.exists=The Web Projects folder already exists: {0} @@ -455,3 +456,8 @@ patch.fixBpmPackages.invalidBootsrapStore=Bootstrap store has not been set patch.fixBpmPackages.emptyContainer={0} node has no children patch.alterJBPM331CLOBcolumnsToNvarchar.description=Altering CLOB columns in the jBPM 3.3.1 tables to introduce Unicode characters support for jBPM 3.3.1 + +patch.avmIndexChildEntriesLower.description=Add an indexed column to avm_child_entries that allows case-insensitive querying of AVM files by name + +patch.imapUnsubscribedAspect.description=Patch to remove deprecated "imap:nonSubscribed" aspect from folders. +patch.imapUnsubscribedAspect.result.removed="imap:nonSubscribed" aspect was successfully removed from unsubscribed folders. diff --git a/config/alfresco/messages/patch-service_it.properties b/config/alfresco/messages/patch-service_it.properties index e367a84b2f..4bcb96b00a 100755 --- a/config/alfresco/messages/patch-service_it.properties +++ b/config/alfresco/messages/patch-service_it.properties @@ -152,6 +152,7 @@ patch.siteLoadPatch.description=Loads a sample site into the repository. patch.siteLoadPatch.exists=The Site {0} already exists and so could not be imported patch.siteLoadPatch.noBootstrapViews=No bootstrap views were given for importing Site {0} - please check the bootstrap extension bean configuration patch.siteLoadPatch.result=Site {0} imported. +patch.siteLoadPatch.siteNotCreated=The site {0} is only created for new installs. patch.wcmFolders.description=Ensures the existance of the WCM specific 'Web Projects' and 'Web Forms' folders. patch.wcmFolders.webprojects.result.exists=The Web Projects folder already exists: {0} @@ -455,3 +456,8 @@ patch.fixBpmPackages.invalidBootsrapStore=Bootstrap store has not been set patch.fixBpmPackages.emptyContainer={0} node has no children patch.alterJBPM331CLOBcolumnsToNvarchar.description=Altering CLOB columns in the jBPM 3.3.1 tables to introduce Unicode characters support for jBPM 3.3.1 + +patch.avmIndexChildEntriesLower.description=Add an indexed column to avm_child_entries that allows case-insensitive querying of AVM files by name + +patch.imapUnsubscribedAspect.description=Patch to remove deprecated "imap:nonSubscribed" aspect from folders. +patch.imapUnsubscribedAspect.result.removed="imap:nonSubscribed" aspect was successfully removed from unsubscribed folders. diff --git a/config/alfresco/messages/patch-service_ja.properties b/config/alfresco/messages/patch-service_ja.properties index e367a84b2f..4bcb96b00a 100755 --- a/config/alfresco/messages/patch-service_ja.properties +++ b/config/alfresco/messages/patch-service_ja.properties @@ -152,6 +152,7 @@ patch.siteLoadPatch.description=Loads a sample site into the repository. patch.siteLoadPatch.exists=The Site {0} already exists and so could not be imported patch.siteLoadPatch.noBootstrapViews=No bootstrap views were given for importing Site {0} - please check the bootstrap extension bean configuration patch.siteLoadPatch.result=Site {0} imported. +patch.siteLoadPatch.siteNotCreated=The site {0} is only created for new installs. patch.wcmFolders.description=Ensures the existance of the WCM specific 'Web Projects' and 'Web Forms' folders. patch.wcmFolders.webprojects.result.exists=The Web Projects folder already exists: {0} @@ -455,3 +456,8 @@ patch.fixBpmPackages.invalidBootsrapStore=Bootstrap store has not been set patch.fixBpmPackages.emptyContainer={0} node has no children patch.alterJBPM331CLOBcolumnsToNvarchar.description=Altering CLOB columns in the jBPM 3.3.1 tables to introduce Unicode characters support for jBPM 3.3.1 + +patch.avmIndexChildEntriesLower.description=Add an indexed column to avm_child_entries that allows case-insensitive querying of AVM files by name + +patch.imapUnsubscribedAspect.description=Patch to remove deprecated "imap:nonSubscribed" aspect from folders. +patch.imapUnsubscribedAspect.result.removed="imap:nonSubscribed" aspect was successfully removed from unsubscribed folders. diff --git a/config/alfresco/messages/patch-service_nl.properties b/config/alfresco/messages/patch-service_nl.properties index e367a84b2f..4bcb96b00a 100755 --- a/config/alfresco/messages/patch-service_nl.properties +++ b/config/alfresco/messages/patch-service_nl.properties @@ -152,6 +152,7 @@ patch.siteLoadPatch.description=Loads a sample site into the repository. patch.siteLoadPatch.exists=The Site {0} already exists and so could not be imported patch.siteLoadPatch.noBootstrapViews=No bootstrap views were given for importing Site {0} - please check the bootstrap extension bean configuration patch.siteLoadPatch.result=Site {0} imported. +patch.siteLoadPatch.siteNotCreated=The site {0} is only created for new installs. patch.wcmFolders.description=Ensures the existance of the WCM specific 'Web Projects' and 'Web Forms' folders. patch.wcmFolders.webprojects.result.exists=The Web Projects folder already exists: {0} @@ -455,3 +456,8 @@ patch.fixBpmPackages.invalidBootsrapStore=Bootstrap store has not been set patch.fixBpmPackages.emptyContainer={0} node has no children patch.alterJBPM331CLOBcolumnsToNvarchar.description=Altering CLOB columns in the jBPM 3.3.1 tables to introduce Unicode characters support for jBPM 3.3.1 + +patch.avmIndexChildEntriesLower.description=Add an indexed column to avm_child_entries that allows case-insensitive querying of AVM files by name + +patch.imapUnsubscribedAspect.description=Patch to remove deprecated "imap:nonSubscribed" aspect from folders. +patch.imapUnsubscribedAspect.result.removed="imap:nonSubscribed" aspect was successfully removed from unsubscribed folders. diff --git a/config/alfresco/messages/site-model_de.properties b/config/alfresco/messages/site-model_de.properties new file mode 100755 index 0000000000..6fba171d12 --- /dev/null +++ b/config/alfresco/messages/site-model_de.properties @@ -0,0 +1,5 @@ +# Display labels for Site Model +st_siteModel.property.st_sitePreset.title=Site Voreinstellungen +st_siteModel.property.st_sitePreset.description=Site Voreinstellungen +st_siteModel.property.st_siteVisibility.title=Site Sichtbarkeit +st_siteModel.property.st_siteVisibility.description=Site Sichtbarkeit \ No newline at end of file diff --git a/config/alfresco/messages/site-model_es.properties b/config/alfresco/messages/site-model_es.properties new file mode 100755 index 0000000000..f390332084 --- /dev/null +++ b/config/alfresco/messages/site-model_es.properties @@ -0,0 +1,5 @@ +# Display labels for Site Model +st_siteModel.property.st_sitePreset.title=Plantilla de sitio +st_siteModel.property.st_sitePreset.description=Plantilla de sitio +st_siteModel.property.st_siteVisibility.title=Visibilidad del sitio +st_siteModel.property.st_siteVisibility.description=Visibilidad del sitio \ No newline at end of file diff --git a/config/alfresco/messages/site-model_fr.properties b/config/alfresco/messages/site-model_fr.properties new file mode 100755 index 0000000000..c460278cee --- /dev/null +++ b/config/alfresco/messages/site-model_fr.properties @@ -0,0 +1,5 @@ +# Display labels for Site Model +st_siteModel.property.st_sitePreset.title=Pr\u00e9configuration de site +st_siteModel.property.st_sitePreset.description=Pr\u00e9configuration de site +st_siteModel.property.st_siteVisibility.title=Visibilit\u00e9 du site +st_siteModel.property.st_siteVisibility.description=Visibilit\u00e9 du site \ No newline at end of file diff --git a/config/alfresco/messages/site-model_it.properties b/config/alfresco/messages/site-model_it.properties new file mode 100755 index 0000000000..4536dd618e --- /dev/null +++ b/config/alfresco/messages/site-model_it.properties @@ -0,0 +1,5 @@ +# Display labels for Site Model +st_siteModel.property.st_sitePreset.title=Modello di sito +st_siteModel.property.st_sitePreset.description=Modello di sito +st_siteModel.property.st_siteVisibility.title=Visibilit\u00e0 del sito +st_siteModel.property.st_siteVisibility.description=Visibilit\u00e0 del sito \ No newline at end of file diff --git a/config/alfresco/messages/site-model_ja.properties b/config/alfresco/messages/site-model_ja.properties new file mode 100755 index 0000000000..642eecd667 --- /dev/null +++ b/config/alfresco/messages/site-model_ja.properties @@ -0,0 +1,5 @@ +# Display labels for Site Model +st_siteModel.property.st_sitePreset.title=\u30b5\u30a4\u30c8\u306e\u4e8b\u524d\u8a2d\u5b9a +st_siteModel.property.st_sitePreset.description=\u30b5\u30a4\u30c8\u306e\u4e8b\u524d\u8a2d\u5b9a +st_siteModel.property.st_siteVisibility.title=\u30b5\u30a4\u30c8\u306e\u516c\u958b\u30ec\u30d9\u30eb +st_siteModel.property.st_siteVisibility.description=\u30b5\u30a4\u30c8\u306e\u516c\u958b\u30ec\u30d9\u30eb \ No newline at end of file diff --git a/config/alfresco/messages/templates-messages.properties b/config/alfresco/messages/templates-messages.properties index 91a58e7e2e..4c7e82d7d1 100644 --- a/config/alfresco/messages/templates-messages.properties +++ b/config/alfresco/messages/templates-messages.properties @@ -9,20 +9,9 @@ templates.show_audit.current_document_audit_info=Current Document Audit Info templates.show_audit.name=Name: templates.show_audit.user_name=User Name templates.show_audit.application=Application -templates.show_audit.service=Service templates.show_audit.method=Method templates.show_audit.timestamp=Timestamp templates.show_audit.values=Audit Entry Values -templates.show_audit.failed=Failed -templates.show_audit.message=Message -templates.show_audit.arg_1=Arg 1 -templates.show_audit.arg_2=Arg 2 -templates.show_audit.arg_3=Arg 3 -templates.show_audit.arg_4=Arg 4 -templates.show_audit.arg_5=Arg 5 -templates.show_audit.return=Return -templates.show_audit.thowable=Throwable -templates.show_audit.tx=TX templates.show_audit.current_space_audit_info=Current Space Audit Info: #recent_docs.ftl diff --git a/config/alfresco/messages/wdr-messages_fr.properties b/config/alfresco/messages/wdr-messages_fr.properties index ca9cc9aec4..199e97f0db 100755 --- a/config/alfresco/messages/wdr-messages_fr.properties +++ b/config/alfresco/messages/wdr-messages_fr.properties @@ -7,6 +7,6 @@ wdr.err.unable_prepare_missing_file=Pr\u00e9paration impossible, fichier tempora wdr.err.invalid_ticket=D\u00e9ploiement expir\u00e9 ou ticket non valide. {0} wdr.err.unable_commit=Validation impossible -wdr.avm.snapshot_tag=D\u00e9ploiement depuis le magasin : {0}, version : {1} -wdr.avm.snapshot_description=D\u00e9ploiement depuis le magasin : {0}, version : {1} +wdr.avm.snapshot_tag=D\u00e9ploiement depuis le store : {0}, version : {1} +wdr.avm.snapshot_description=D\u00e9ploiement depuis le store : {0}, version : {1} diff --git a/config/alfresco/node-services-context.xml b/config/alfresco/node-services-context.xml index 83df8e0955..b9c7d6acc2 100644 --- a/config/alfresco/node-services-context.xml +++ b/config/alfresco/node-services-context.xml @@ -207,6 +207,9 @@ ${index.tracking.minRecordPurgeAgeDays} + + ${index.tracking.purgeSize} + diff --git a/config/alfresco/patch/patch-services-context.xml b/config/alfresco/patch/patch-services-context.xml index 61668d4f23..2a8a803cb9 100644 --- a/config/alfresco/patch/patch-services-context.xml +++ b/config/alfresco/patch/patch-services-context.xml @@ -3080,5 +3080,16 @@ classpath:alfresco/dbscripts/upgrade/3.4/${db.script.dialect}/AVM-index-child-entries-lower.sql + + + + + + + + + classpath:alfresco/dbscripts/upgrade/4.0/${db.script.dialect}/ActivitiTaskIdIndexes.sql + + diff --git a/config/alfresco/public-services-security-context.xml b/config/alfresco/public-services-security-context.xml index f8c3a9089a..0ff8669192 100644 --- a/config/alfresco/public-services-security-context.xml +++ b/config/alfresco/public-services-security-context.xml @@ -83,6 +83,9 @@ + + + @@ -92,6 +95,9 @@ + + ${security.anyDenyDenies} + @@ -314,9 +320,9 @@ - + - + @@ -372,8 +378,8 @@ org.alfresco.service.cmr.repository.NodeService.getAspects=ACL_NODE.0.sys:base.ReadProperties org.alfresco.service.cmr.repository.NodeService.deleteNode=ACL_NODE.0.sys:base.DeleteNode org.alfresco.service.cmr.repository.NodeService.addChild=ACL_NODE.0.sys:base.CreateChildren,ACL_NODE.1.sys:base.ReadProperties - org.alfresco.service.cmr.repository.NodeService.removeChild=ACL_NODE.0.sys:base.DeleteChildren,ACL_NODE.1.sys:base.DeleteNode - org.alfresco.service.cmr.repository.NodeService.removeChildAssociation=ACL_PARENT.0.sys:base.DeleteChildren,ACL_NODE.0.sys:base.DeleteNode + org.alfresco.service.cmr.repository.NodeService.removeChild=ACL_NODE.0.sys:base.DeleteChildren,ACL_PRI_CHILD_ASSOC_ON_CHILD.0.1.sys:base.DeleteNode + org.alfresco.service.cmr.repository.NodeService.removeChildAssociation=ACL_PARENT.0.sys:base.DeleteChildren,ACL_PRI_CHILD_ASSOC_ON_CHILD.0.sys:base.DeleteNode org.alfresco.service.cmr.repository.NodeService.getProperties=ACL_NODE.0.sys:base.ReadProperties org.alfresco.service.cmr.repository.NodeService.getProperty=ACL_NODE.0.sys:base.ReadProperties org.alfresco.service.cmr.repository.NodeService.setProperties=ACL_NODE.0.sys:base.WriteProperties diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties index e786b4761b..b9018bd2d5 100644 --- a/config/alfresco/repository.properties +++ b/config/alfresco/repository.properties @@ -100,6 +100,9 @@ index.tracking.disableInTransactionIndexing=false # with a more recent backup of the Lucene indexes or the indexes will have to be fully rebuilt. # Use -1 to disable purging. This can be switched on at any stage. index.tracking.minRecordPurgeAgeDays=30 +# Unused transactions will be purged in chunks determined by commit time boundaries. 'index.tracking.purgeSize' specifies the size +# of the chunk (in ms). Default is a couple of hours. +index.tracking.purgeSize=7200000 # Reindexing of missing content is by default 'never' carried out. # The cron expression below can be changed to control the timing of this reindexing. @@ -378,7 +381,7 @@ db.pool.abandoned.log=false audit.enabled=true audit.tagging.enabled=true audit.alfresco-access.enabled=false -audit.alfresco-access.sub-events.enabled=false +audit.alfresco-access.sub-actions.enabled=false audit.cmischangelog.enabled=false audit.dod5015.enabled=false # Setting this flag to true will force startup failure when invalid audit configurations are detected @@ -566,6 +569,7 @@ img.root=./ImageMagick img.dyn=${img.root}/lib img.exe=${img.root}/bin/convert swf.exe=./bin/pdf2swf +swf.languagedir=. # Thumbnail Service system.thumbnail.generate=true @@ -645,12 +649,12 @@ V2.1-A.fixes.to.schema=0 authentication.chain=alfrescoNtlm1:alfrescoNtlm # Do authentication tickets expire or live for ever? -authentication.ticket.ticketsExpire=false +authentication.ticket.ticketsExpire=true # If ticketsEpire is true then how they should expire? # Valid values are: AFTER_INACTIVITY, AFTER_FIXED_TIME, DO_NOT_EXPIRE # The default is AFTER_FIXED_TIME -authentication.ticket.expiryMode=AFTER_FIXED_TIME +authentication.ticket.expiryMode=AFTER_INACTIVITY # If authentication.ticket.ticketsExpire is true and # authentication.ticket.expiryMode is AFTER_FIXED_TIME or AFTER_INACTIVITY, @@ -797,6 +801,10 @@ deployment.filesystem.default.metadatadir=${deployment.filesystem.metadatadir}/d orphanReaper.lockRefreshTime=60000 orphanReaper.lockTimeOut=3600000 + +# security +security.anyDenyDenies=true + # # Encryption properties # @@ -904,4 +912,7 @@ system.content.caching.maxFileSizeMB=0 mybatis.useLocalCaches=false -fileFolderService.checkHidden.enabled=true \ No newline at end of file +fileFolderService.checkHidden.enabled=true + + +ticket.cleanup.cronExpression=0 0 * * * ? \ No newline at end of file diff --git a/config/alfresco/scheduled-jobs-context.xml b/config/alfresco/scheduled-jobs-context.xml index e8a543d93d..eafb4b93f6 100644 --- a/config/alfresco/scheduled-jobs-context.xml +++ b/config/alfresco/scheduled-jobs-context.xml @@ -273,4 +273,31 @@ + + + + + org.alfresco.repo.security.authentication.TicketCleanupJob + + + + + + + + + + + + + + + + + + + ${ticket.cleanup.cronExpression} + + + diff --git a/config/alfresco/subsystems/fileServers/default/network-protocol-context.xml b/config/alfresco/subsystems/fileServers/default/network-protocol-context.xml index 36577b79a3..35c240a145 100644 --- a/config/alfresco/subsystems/fileServers/default/network-protocol-context.xml +++ b/config/alfresco/subsystems/fileServers/default/network-protocol-context.xml @@ -146,26 +146,38 @@ - ^\._.* - 30000 + ^\._.txt* + 60000 HIGH [0-9A-F]{8}+$ - 30000 + 60000 HIGH ~WRD.*.TMP - 30000 + 60000 HIGH [0-9A-F]*.TMP$ - 30000 + 60000 + HIGH + + + + .*D_[0-9]*.TMP$ + 60000 + HIGH + + + + ^[^\._].*[0-9].pptx$ + 60000 HIGH diff --git a/config/alfresco/subsystems/thirdparty/default/swf-transform-context.xml b/config/alfresco/subsystems/thirdparty/default/swf-transform-context.xml index a709a87530..9a1fa252f9 100644 --- a/config/alfresco/subsystems/thirdparty/default/swf-transform-context.xml +++ b/config/alfresco/subsystems/thirdparty/default/swf-transform-context.xml @@ -26,7 +26,7 @@ - ${swf.exe} -T ${flashVersion} ${swf.encoder.params} ${source} -o ${target} + ${swf.exe} -T ${flashVersion} ${swf.encoder.params} ${source} -o ${target} -s languagedir=${swf.languagedir} diff --git a/config/alfresco/version.properties b/config/alfresco/version.properties index d0eeed8784..a9aa890379 100644 --- a/config/alfresco/version.properties +++ b/config/alfresco/version.properties @@ -19,4 +19,4 @@ version.build=@build-number@ # Schema number -version.schema=6003 +version.schema=6004 diff --git a/source/java/org/alfresco/filesys/auth/cifs/CifsAuthenticatorBase.java b/source/java/org/alfresco/filesys/auth/cifs/CifsAuthenticatorBase.java index 0e1c624205..02ccbb1208 100644 --- a/source/java/org/alfresco/filesys/auth/cifs/CifsAuthenticatorBase.java +++ b/source/java/org/alfresco/filesys/auth/cifs/CifsAuthenticatorBase.java @@ -31,6 +31,7 @@ import org.alfresco.jlan.server.filesys.DiskDeviceContext; import org.alfresco.jlan.server.filesys.DiskInterface; import org.alfresco.jlan.server.filesys.DiskSharedDevice; import org.alfresco.jlan.server.filesys.SrvDiskInfo; +import org.alfresco.jlan.smb.server.SMBSrvException; import org.alfresco.model.ContentModel; import org.alfresco.repo.management.subsystems.ActivateableBean; import org.alfresco.repo.security.authentication.AuthenticationComponent; @@ -64,8 +65,7 @@ public abstract class CifsAuthenticatorBase extends CifsAuthenticator implements { // Logging - /** The Constant logger. */ - protected static final Log logger = LogFactory.getLog("org.alfresco.smb.protocol.auth"); + protected static final Log logger = LogFactory.getLog(CifsAuthenticatorBase.class); // MD4 hash decoder @@ -342,7 +342,7 @@ public abstract class CifsAuthenticatorBase extends CifsAuthenticator implements * @param client * ClientInfo */ - protected final void getHomeFolderForUser(final ClientInfo client) + protected final void getHomeFolderForUser(final ClientInfo client) { // Check if the client is an Alfresco client, and not a null logon @@ -356,7 +356,7 @@ public abstract class CifsAuthenticatorBase extends CifsAuthenticator implements doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() { - public Object execute() throws Throwable + public Object execute() throws SMBSrvException { NodeRef homeSpaceRef = (NodeRef) getNodeService().getProperty( getPersonService().getPerson(client.getUserName()), ContentModel.PROP_HOMEFOLDER); @@ -364,17 +364,25 @@ public abstract class CifsAuthenticatorBase extends CifsAuthenticator implements return null; } }); - } + } /** * Map the case insensitive logon name to the internal person object user name. + * And optionally check whether the user is enabled. + * * * @param userName * String - * @return String + * @param checkEnabled + * @return the user name */ - protected final String mapUserNameToPerson(final String userName) + public final String mapUserNameToPerson(final String userName, final boolean checkEnabled) { + if(logger.isDebugEnabled()) + { + logger.debug("mapUserNameToPerson userName:" + userName + ", checkEnabled:" + checkEnabled); + } + // Do the lookup as the system user return AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() { @@ -385,8 +393,6 @@ public abstract class CifsAuthenticatorBase extends CifsAuthenticator implements public String execute() throws Throwable { - // Get the home folder for the user - String personName = getPersonService().getUserIdentifier(userName); // Check if the person exists @@ -396,9 +402,32 @@ public abstract class CifsAuthenticatorBase extends CifsAuthenticator implements // Force creation of a person if possible getPersonService().getPerson(userName); personName = getPersonService().getUserIdentifier(userName); - return personName == null ? userName : personName; } - return personName; + + if(checkEnabled && personName != null) + { + /** + * Check the authenticator is enabled + */ + boolean isAuthenticationEnabled = getAuthenticationService().getAuthenticationEnabled(personName); + if(!isAuthenticationEnabled) + { + logger.debug("autentication service says user is not enabled"); + throw new AuthenticationException("Authentication not enabled for:" + userName); + } + + /** + * Check the person is enabled + */ + boolean isEnabled = personService.isEnabled(personName); + if(!isEnabled) + { + logger.debug("person service says user is not enabled"); + throw new AuthenticationException("Authentication not enabled for person:" + userName); + } + } + + return personName == null ? userName : personName; } }); } @@ -410,8 +439,8 @@ public abstract class CifsAuthenticatorBase extends CifsAuthenticator implements * * @param client ClientInfo or null to clear the context */ - public void setCurrentUser(final ClientInfo client) { - + public void setCurrentUser(final ClientInfo client) + { // Check the account type and setup the authentication context // No need for a transaction to clear the context @@ -541,7 +570,7 @@ public abstract class CifsAuthenticatorBase extends CifsAuthenticator implements * @param cInfo * ClientInfo */ - protected final void checkForAdminUserName(final ClientInfo cInfo) + protected final void checkForAdminUserName(final ClientInfo cInfo) { // Check if the user name is an administrator @@ -549,7 +578,7 @@ public abstract class CifsAuthenticatorBase extends CifsAuthenticator implements doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() { - public Object execute() throws Throwable + public Object execute() { if (cInfo.getLogonType() == ClientInfo.LogonNormal && getAuthorityService().isAdminAuthority(cInfo.getUserName())) diff --git a/source/java/org/alfresco/filesys/auth/cifs/EnterpriseCifsAuthenticator.java b/source/java/org/alfresco/filesys/auth/cifs/EnterpriseCifsAuthenticator.java index f171737737..ef2d68dda4 100644 --- a/source/java/org/alfresco/filesys/auth/cifs/EnterpriseCifsAuthenticator.java +++ b/source/java/org/alfresco/filesys/auth/cifs/EnterpriseCifsAuthenticator.java @@ -38,7 +38,6 @@ import javax.security.sasl.RealmCallback; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.filesys.alfresco.AlfrescoClientInfo; -import org.alfresco.jlan.debug.Debug; import org.alfresco.jlan.server.auth.AuthenticatorException; import org.alfresco.jlan.server.auth.ClientInfo; import org.alfresco.jlan.server.auth.NTLanManAuthContext; @@ -73,6 +72,8 @@ import org.alfresco.repo.security.authentication.AuthenticationException; import org.alfresco.repo.security.authentication.NTLMMode; import org.alfresco.repo.security.authentication.ntlm.NLTMAuthenticator; import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.ietf.jgss.Oid; import org.springframework.extensions.config.ConfigElement; @@ -88,6 +89,9 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Constants // // Default login configuration entry name + protected static final Log logger = LogFactory.getLog(EnterpriseCifsAuthenticator.class); + + // logger is defined in base class. private static final String LoginConfigEntry = "AlfrescoCIFS"; @@ -221,7 +225,9 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Debug if ( logger.isInfoEnabled() && enaTktCracking) + { logger.info("CIFS Kerberos authentication, ticket cracking enabled (for mutual authentication)"); + } } /** @@ -251,8 +257,10 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement setPassword(srvPassword.getValue()); } else + { throw new InvalidConfigurationException("CIFS service account password not specified"); - + } + // Get the login configuration entry name ConfigElement loginEntry = params.getChild("LoginEntry"); @@ -265,7 +273,9 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement setJaasConfigEntryName(loginEntry.getValue()); } else + { throw new InvalidConfigurationException("Invalid login entry specified"); + } } setDisableNTLM(params.getChild("disableNTLM") != null); @@ -338,7 +348,9 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Debug if (logger.isErrorEnabled()) + { logger.error("CIFS Kerberos authenticator error", ex); + } throw new InvalidConfigurationException("Failed to login CIFS server service"); } @@ -350,10 +362,10 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement m_accountName = princ.getName(); - // DEBUG - if (logger.isDebugEnabled()) + { logger.debug("Logged on using principal " + m_accountName); + } // Create the Oid list for the SPNEGO NegTokenInit, include NTLMSSP for fallback @@ -363,9 +375,7 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement if (logger.isDebugEnabled()) { - logger.debug("Enabling mechTypes :-"); - logger.debug(" Kerberos5"); - logger.debug(" MS-Kerberos5"); + logger.debug("Enabling mechTypes :-Kerberos5 MS-Kerberos5"); } // Always enable Kerberos @@ -380,7 +390,9 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // DEBUG if (logger.isDebugEnabled()) - logger.debug(" NTLMSSP"); + { + logger.debug(" Enabling NTLMSSP"); + } } // Indicate that SPNEGO security blobs are being used @@ -409,14 +421,13 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement if (!isKerberosEnabled() && (!(getAuthenticationComponent() instanceof NLTMAuthenticator) || getNTLMAuthenticator().getNTLMMode() != NTLMMode.MD4_PROVIDER)) { - // Log an error - - logger.error("No valid CIFS authentication combination available"); - logger.error("Either enable Kerberos support or use an SSO-enabled authentication component that supports MD4 hashed passwords"); - + if(logger.isDebugEnabled()) + { + logger.debug("No valid CIFS authentication combination available, Either enable Kerberos support or use an SSO-enabled authentication component that supports MD4 hashed passwords"); + } // Throw an exception to stop the CIFS server startup - throw new AlfrescoRuntimeException("Invalid CIFS authenticator configuration"); + throw new AlfrescoRuntimeException("No valid CIFS authentication combination available, Either enable Kerberos support or use an SSO-enabled authentication component that supports MD4 hashed passwords"); } } @@ -488,8 +499,9 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Debug if (logger.isErrorEnabled()) - logger.error("Error creating SPNEGO NegTokenInit blob", ex); - + { + logger.error("Unable to create SPNEGO NegTokenInit blob", ex); + } throw new AuthenticatorException("Failed to create SPNEGO NegTokenInit blob"); } @@ -644,49 +656,97 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement } /** - * Process the CIFS session setup request packet and build the session setup response - * + * Process the CIFS session setup request packet and build the session setup response. + *

+ * This is the boundary between alfresco and JLAN. So is responsible for logging and + * ensuring that the exceptions are correct for JLAN. + *

* @param sess SMBSrvSession * @param reqPkt SMBSrvPacket * @exception SMBSrvException */ public void processSessionSetup(final SMBSrvSession sess, final SMBSrvPacket reqPkt) + throws SMBSrvException + { + try + { + processAlfrescoSessionSetup(sess, reqPkt); + } + catch (SMBSrvException e) + { + /* + * A JLAN SMBSrvException is not human readable so we need to log the + * error before throwing, rather than logging the error here. + */ + if(logger.isDebugEnabled()) + { + logger.debug("Returning SMBSrvException to JLAN", e); + } + throw e; + } + catch (AlfrescoRuntimeException a) + { + Throwable c = a.getCause(); + + if(c != null) + { + if(a.getCause() instanceof SMBSrvException) + { + logger.error(c.getMessage(), c); + throw (SMBSrvException)c; + } + } + logger.error(a.getMessage(), a); + throw new SMBSrvException( SMBStatus.NTAccessDenied, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); + + } + catch (Throwable t) + { + logger.error(t.getMessage(), t); + throw new SMBSrvException( SMBStatus.NTAccessDenied, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); + } + } + + /** + * Internal setup method for alfresco. There's lots of CIFS specific stuff here that should + * be in JLAN. + * + * @param sess the JLAN session. + * @param reqPkt the CIFS request packet + * @throws SMBSrvException + */ + private void processAlfrescoSessionSetup(final SMBSrvSession sess, final SMBSrvPacket reqPkt) throws SMBSrvException { + logger.debug("Start process Alfresco Session Setup"); + // Check that the received packet looks like a valid NT session setup andX request - if (reqPkt.checkPacketIsValid(12, 0) == false) - throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv); - + { + if(logger.isErrorEnabled()) + { + logger.error("Invalid packet received, return SMBStatus.NTInvalidParameter"); + } + throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.ErrSrv, SMBStatus.SRVNonSpecificError); + } // Check if the request is using security blobs or the older hashed password format if ( reqPkt.getParameterCount() == 13) { - try + logger.debug("parameter count == 13, do Hashed Password Logon"); + + doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() { - // Start a transaction - - doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() - { - - public Object execute() throws Throwable - { - // Process the hashed password session setup - - doHashedPasswordLogon(sess, reqPkt); - return null; - } - }); - } - catch ( Exception ex) - { - // Convert to an access denied exception - - throw new SMBSrvException( SMBStatus.NTAccessDenied, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); - } + public Object execute() throws SMBSrvException + { + // Process the hashed password session setup + logger.debug("about to call doHashedPasswordLogon"); + doHashedPasswordLogon(sess, reqPkt); + return null; + } + }); // Hashed password processing complete - return; } @@ -718,35 +778,40 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement String domain = ""; - if (reqPkt.hasMoreData()) { - + if (reqPkt.hasMoreData()) + { // Extract the callers domain name - domain = reqPkt.unpackString(isUni); if (domain == null) - throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv); + { + logger.error("domain is null, return SMBStatus.NTInvalidParameter"); + throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.ErrSrv, SMBStatus.SRVNonSpecificError); + } } // Extract the clients native operating system String clientOS = ""; - if (reqPkt.hasMoreData()) { + if (reqPkt.hasMoreData()) + { // Extract the callers operating system name clientOS = reqPkt.unpackString(isUni); if (clientOS == null) - throw new SMBSrvException( SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv); + { + logger.error("clientOS is null, return SMBStatus.NTInvalidParameter"); + throw new SMBSrvException( SMBStatus.NTInvalidParameter, SMBStatus.ErrSrv, SMBStatus.SRVNonSpecificError); + } } - // DEBUG - if (logger.isDebugEnabled()) - logger.debug("NT Session setup " + (useRawNTLMSSP() ? "NTLMSSP" : "SPNEGO") + ", MID=" + reqPkt.getMultiplexId() + ", UID=" + reqPkt.getUserId() + ", PID=" + reqPkt.getProcessId()); - + { + logger.debug("NT Session setup " + (useRawNTLMSSP() ? "NTLMSSP" : "SPNEGO") + ", MID=" + reqPkt.getMultiplexId() + ", UID=" + reqPkt.getUserId() + ", PID=" + reqPkt.getProcessId()); + } // Store the client maximum buffer size, maximum multiplexed requests count and client capability flags sess.setClientMaximumBufferSize(maxBufSize != 0 ? maxBufSize : SMBSrvSession.DefaultBufferSize); @@ -764,7 +829,9 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Set the remote address, if available if ( sess.hasRemoteAddress()) - client.setClientAddress(sess.getRemoteAddress().getHostAddress()); + { + client.setClientAddress(sess.getRemoteAddress().getHostAddress()); + } // Set the process id for this client, for multi-stage logons @@ -780,27 +847,28 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement final boolean isNTLMSSP; try - { - + { // Check if the blob has the NTLMSSP signature - - if ( secBlobLen >= NTLM.Signature.length) { - + if ( secBlobLen >= NTLM.Signature.length) + { // Check for the NTLMSSP signature - int idx = 0; while ( idx < NTLM.Signature.length && buf[secBlobPos + idx] == NTLM.Signature[ idx]) + { idx++; + } isNTLMSSP = ( idx == NTLM.Signature.length); } - else { + else + { isNTLMSSP = false; } - - // Start a transaction - respBlob = doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + /** + * Extracted parameters from CIFS, Now try and do the logon + */ + respBlob = doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() { public byte[] execute() throws Throwable @@ -820,15 +888,17 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement return doSpnegoSessionSetup(sess, client, buf, secBlobPos, secBlobLen, isUni); } } - }); - + }); } catch ( Exception ex) { - // Cleanup any stored context - sess.removeSetupObject( client.getProcessId()); + if( ex instanceof SMBSrvException) + { + throw (SMBSrvException)ex; + } + // Convert to an access denied exception if necessary if (ex instanceof AlfrescoRuntimeException && ex.getCause() instanceof SMBSrvException) @@ -837,15 +907,20 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement } else { + logger.error("Access denied", ex); throw new SMBSrvException( SMBStatus.NTAccessDenied, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } - // Debug + /* + * We have logged on - so set up the response + */ - if ( logger.isDebugEnabled() && sess.hasDebug(SMBSrvSession.DBG_NEGOTIATE)) + if ( logger.isDebugEnabled()) + { logger.debug("User " + client.getUserName() + " logged on " + (client != null ? " (type " + client.getLogonTypeString() + ")" : "")); - + } + // Update the client information if not already set if ( sess.getClientInformation() == null || @@ -893,23 +968,24 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement if ( reqLen > respPkt.getAvailableLength()) { - try { + try + { // Allocate a new buffer for the response respPkt = sess.getPacketPool().allocatePacket(respPkt.getByteOffset() + reqLen, reqPkt); } - catch (NoPooledMemoryException ex) { - - // DEBUG - - if ( Debug.EnableDbg && hasDebug()) - Debug.println("Authenticator failed to allocate packet from pool, reqSiz=" - + (respPkt.getByteOffset() + respLen)); + catch (NoPooledMemoryException ex) + { + if(logger.isErrorEnabled()) + { + logger.error("Authenticator failed to allocate packet from pool, reqSiz=" + + (respPkt.getByteOffset() + respLen)); + } // Return a server error to the client - throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.SRVNoBuffers, SMBStatus.ErrSrv); + throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrSrv, SMBStatus.SRVNoBuffers); } } @@ -972,19 +1048,13 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement if ( uid == VirtualCircuit.InvalidUID) { - // DEBUG - - if ( logger.isDebugEnabled() && sess.hasDebug( SMBSrvSession.DBG_NEGOTIATE)) - logger.debug("Failed to allocate UID for virtual circuit, " + vc); - - // Failed to allocate a UID - - throw new SMBSrvException(SMBStatus.NTTooManySessions, SMBStatus.SRVTooManyUIDs, SMBStatus.ErrSrv); + logger.error("Failed to allocate UID for virtual circuit, " + vc); + // Failed to allocate a UID + throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrSrv, SMBStatus.SRVTooManyUIDs); + } - else if ( logger.isDebugEnabled() && sess.hasDebug( SMBSrvSession.DBG_NEGOTIATE)) { - - // DEBUG - + else if ( logger.isDebugEnabled()) + { logger.debug("Allocated UID=" + uid + " for VC=" + vc); } } @@ -1046,6 +1116,7 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement byte[] secbuf, int secpos, int seclen, boolean unicode) throws SMBSrvException { // Determine the NTLmSSP message type + logger.debug("Start doNTLmsspSessionSetup"); int msgType = NTLMMessage.isNTLMType( secbuf, secpos); byte[] respBlob = null; @@ -1054,21 +1125,25 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement { // DEBUG - if ( logger.isDebugEnabled()) + if ( logger.isErrorEnabled()) { - logger.debug("Invalid NTLMSSP token received"); - logger.debug(" Token=" + HexDump.hexString( secbuf, secpos, seclen, " ")); + logger.error("Invalid NTLMSSP token received, Token= " + HexDump.hexString( secbuf, secpos, seclen, " ")); } // Return a logon failure status - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } // Check for a type 1 NTLMSSP message else if ( msgType == NTLM.Type1) { + if ( logger.isDebugEnabled()) + { + logger.debug("NTLMsspSessionSetup Type1"); + } + // Create the type 1 NTLM message from the token Type1NTLMMessage type1Msg = new Type1NTLMMessage( secbuf, secpos, seclen); @@ -1117,6 +1192,10 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement } else if ( msgType == NTLM.Type3) { + if ( logger.isDebugEnabled()) + { + logger.debug("NTLmsspSessionSetup Type3"); + } // Create the type 3 NTLM message from the token Type3NTLMMessage type3Msg = new Type3NTLMMessage( secbuf, secpos, seclen, unicode); @@ -1130,8 +1209,9 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement sess.removeSetupObject( client.getProcessId()); // Return a logon failure + logger.error("NTLMSSP Logon failure - type 2 message not found"); - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } // Determine if the client sent us NTLMv1 or NTLMv2 @@ -1149,7 +1229,9 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Debug if ( logger.isDebugEnabled()) + { logger.debug("Logged on using NTLMSSP/NTLMv2"); + } } else { @@ -1160,7 +1242,9 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Debug if ( logger.isDebugEnabled()) + { logger.debug("Logged on using NTLMSSP/NTLMv2SessKey"); + } } } else @@ -1172,7 +1256,9 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Debug if ( logger.isDebugEnabled()) + { logger.debug("Logged on using NTLMSSP/NTLMv1"); + } } } @@ -1228,11 +1314,11 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement { // Log the error - logger.error(ex); + logger.error("I/O Error with SPNEGO authentication", ex); // Return a logon failure status - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } // Get the second stage NTLMSSP blob @@ -1270,11 +1356,11 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement { // Log the error - logger.error(ex); + logger.error("Unable to decode the SPNEGO token", ex); // Return a logon failure status - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } // Determine the authentication mechanism the client is using and logon @@ -1321,8 +1407,9 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement } // No valid authentication mechanism + logger.error("No authentication mechanism for SPNEGO found"); - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } else @@ -1333,7 +1420,7 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Return a logon failure status - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } // Generate the NegTokenTarg blob @@ -1348,14 +1435,9 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement } catch ( IOException ex) { - // Debug + logger.error("SPNEGO unable to encode NegTokenTarg", ex); - if ( logger.isDebugEnabled()) - logger.debug("Failed to encode NegTokenTarg", ex); - - // Failed to build response blob - - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } // Return the SPNEGO response blob @@ -1416,8 +1498,8 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement { // Failed to parse AP-REQ - if ( logger.isDebugEnabled()) - logger.debug("Failed to parse AP-REQ, " + ex.toString()); + + logger.error("Kerberos Failed to parse AP-REQ ", ex); // Return a logon failure status @@ -1527,15 +1609,15 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Set the current user to be authenticated, save the authentication token try - { + { AlfrescoClientInfo alfClient = (AlfrescoClientInfo) client; - getAuthenticationComponent().setCurrentUser( mapUserNameToPerson(krbDetails.getUserName())); + getAuthenticationComponent().setCurrentUser( mapUserNameToPerson(krbDetails.getUserName(), true)); alfClient.setAuthenticationTicket(getAuthenticationService().getCurrentTicket() ); } catch (AuthenticationException e) { // Invalid user or max tickets exceeded. Return a logon failure status - + logger.error("invalid user or tickets exceeded"); throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } @@ -1570,26 +1652,22 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement } else { - // Debug + logger.error( "No SPNEGO response, Kerberos logon failed"); - if ( logger.isDebugEnabled()) - logger.debug( "No SPNEGO response, Kerberos logon failed"); - - // Return a logon failure status - + // Return a logon failure status throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } catch (Exception ex) { - // Log the error - - if ( logger.isErrorEnabled()) { - logger.error("Kerberos logon error"); - logger.error(ex); - } + + if(ex instanceof SMBSrvException) + { + throw (SMBSrvException)ex; + } // Return a logon failure status + logger.error("Error during kerberos authentication", ex); throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } @@ -1599,6 +1677,20 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement return negTokenTarg; } + private String normalizeUserId(String externalUserId) throws SMBSrvException + { + try + { + return mapUserNameToPerson(externalUserId, true); + } + catch (AuthenticationException e) + { + // Invalid user. Return a logon failure status + logger.debug("Authentication Exception", e); + throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); + } + } + /** * Perform an NTLMv1 logon using the NTLMSSP type3 message * @@ -1616,11 +1708,11 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement { // NTLMv1 password hashes not accepted - logger.warn("NTLMv1 not accepted, client " + sess.getRemoteName()); + logger.error("NTLMv1 not accepted, client " + sess.getRemoteName()); // Return a logon failure - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } // Get the type 2 message that contains the challenge sent to the client @@ -1653,7 +1745,8 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Get the stored MD4 hashed password for the user, or null if the user does not exist - String md4hash = getNTLMAuthenticator().getMD4HashedPassword(userName); + String normalized = normalizeUserId(userName); + String md4hash = getNTLMAuthenticator().getMD4HashedPassword(normalized); if ( md4hash != null) { @@ -1689,23 +1782,31 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement if ( i != clientHash.length) { // Return a logon failure + + if(logger.isDebugEnabled()) + { + logger.debug("NTLMV1 - hash was not equal"); + } - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } - // Setup the Acegi authenticated user - + /* + * Setup the Alfresco Security (Acegi) context + */ try { - AlfrescoClientInfo alfClient = (AlfrescoClientInfo) client; - getAuthenticationComponent().setCurrentUser( mapUserNameToPerson(userName)); - alfClient.setAuthenticationTicket(getAuthenticationService().getCurrentTicket()); + AlfrescoClientInfo alfClient = (AlfrescoClientInfo) client; + getAuthenticationComponent().setCurrentUser( normalized); + alfClient.setAuthenticationTicket(getAuthenticationService().getCurrentTicket() ); + + } catch (AuthenticationException e) { // Invalid user or max tickets exceeded. Return a logon failure status - + logger.debug("Authentication Exception", e); throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } @@ -1720,13 +1821,14 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement } else { - // Log a warning, user does not exist - - logger.warn("User does not exist, " + userName); + if(logger.isDebugEnabled()) + { + logger.debug("User does not exist, " + userName); + } // Return a logon failure - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } else @@ -1737,7 +1839,7 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Return a logon failure - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } @@ -1761,7 +1863,12 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Return a logon failure - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); + } + + if(logger.isDebugEnabled()) + { + logger.debug("START doNTLMv1Logon:" + client); } // Check if we are using local MD4 password hashes or passthru authentication @@ -1775,7 +1882,9 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // DEBUG if ( logger.isDebugEnabled()) + { logger.debug("Null logon"); + } // Indicate a null logon in the client information @@ -1785,7 +1894,8 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Get the stored MD4 hashed password for the user, or null if the user does not exist - String md4hash = getNTLMAuthenticator().getMD4HashedPassword(client.getUserName()); + String normalized = normalizeUserId(client.getUserName()); + String md4hash = getNTLMAuthenticator().getMD4HashedPassword(normalized); if ( md4hash != null) { @@ -1816,6 +1926,9 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement } catch (NoSuchAlgorithmException ex) { + logger.error("Unable to encrypt challenge", ex); + + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrSrv, SMBStatus.SRVInternalServerError); } // Validate the password @@ -1832,8 +1945,12 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement if ( i != clientHash.length) { // Return a logon failure + if(logger.isDebugEnabled()) + { + logger.debug("NTLMV1 access denied - wrong password"); + } - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrSrv, SMBStatus.SRVBadPassword); } } @@ -1842,12 +1959,13 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement try { AlfrescoClientInfo alfClient = (AlfrescoClientInfo) client; - getAuthenticationComponent().setCurrentUser( mapUserNameToPerson(client.getUserName())); + getAuthenticationComponent().setCurrentUser( normalized); alfClient.setAuthenticationTicket(getAuthenticationService().getCurrentTicket()); } catch (AuthenticationException e) { // Invalid user or max tickets exceeded. Return a logon failure status + logger.debug("Authentication exception", e); throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } @@ -1867,18 +1985,18 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Return a logon failure - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } else { // Log a warning, authentication component does not support MD4 hashed passwords - logger.warn("Authentication component does not support MD4 password hashes"); + logger.error("Authentication component does not support MD4 password hashes"); // Return a logon failure - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } @@ -1900,6 +2018,11 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Check if we are using local MD4 password hashes or passthru authentication + if(logger.isDebugEnabled()) + { + logger.debug("START doNTLMv2Logon:" + client); + } + if ( getNTLMAuthenticator().getNTLMMode() == NTLMMode.MD4_PROVIDER) { // Get the NTLM logon details @@ -1913,7 +2036,9 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // DEBUG if ( logger.isDebugEnabled()) + { logger.debug("Null logon"); + } // Indicate a null logon in the client information @@ -1923,7 +2048,8 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Get the stored MD4 hashed password for the user, or null if the user does not exist - String md4hash = getNTLMAuthenticator().getMD4HashedPassword(userName); + String normalized = normalizeUserId(userName); + String md4hash = getNTLMAuthenticator().getMD4HashedPassword(normalized); if ( md4hash != null) { @@ -1953,19 +2079,24 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement if ( i != clientHmac.length) { // Return a logon failure + if(logger.isDebugEnabled()) + { + logger.debug("wrong password"); + } - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrSrv, SMBStatus.SRVBadPassword); } } // Setup the Acegi authenticated user AlfrescoClientInfo alfClient = (AlfrescoClientInfo) client; - getAuthenticationComponent().setCurrentUser( mapUserNameToPerson( userName)); + getAuthenticationComponent().setCurrentUser( normalized); alfClient.setAuthenticationTicket(getAuthenticationService().getCurrentTicket()); + // Store the full user name in the client information, indicate that this is not a guest logon - + client.setUserName( userName.toLowerCase()); client.setGuest( false); @@ -1975,31 +2106,35 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement } catch ( Exception ex) { + if(ex instanceof SMBSrvException) + { + throw (SMBSrvException)ex; + } + // Log the error if (ex instanceof AuthenticationException) { - logger.debug(ex); + logger.debug(ex.getMessage(), ex); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } else { - logger.error(ex); + logger.error(ex.getMessage(), ex); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } - // Return a logon failure - - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); } } else { // Log a warning, user does not exist - logger.warn("User does not exist, " + userName); + logger.warn("MD4Hash for User does not exist, " + userName); // Return a logon failure - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } else @@ -2010,7 +2145,7 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Return a logon failure - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } @@ -2026,6 +2161,11 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement { // Check if we are using local MD4 password hashes or passthru authentication + if(logger.isDebugEnabled()) + { + logger.debug("START doNTLMv2Logon:" + client); + } + if ( getNTLMAuthenticator().getNTLMMode() == NTLMMode.MD4_PROVIDER) { // Check for a null logon @@ -2035,7 +2175,9 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // DEBUG if ( logger.isDebugEnabled()) + { logger.debug("Null logon"); + } // Indicate a null logon in the client information @@ -2045,7 +2187,8 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Get the stored MD4 hashed password for the user, or null if the user does not exist - String md4hash = getNTLMAuthenticator().getMD4HashedPassword(client.getUserName()); + String normalized = normalizeUserId(client.getUserName()); + String md4hash = getNTLMAuthenticator().getMD4HashedPassword(normalized); if ( md4hash != null) { @@ -2084,16 +2227,17 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement if ( i != clientHmac.length) { + logger.debug("bad client hmac"); + // Return a logon failure - - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrSrv, SMBStatus.SRVBadPassword); } } // Setup the Acegi authenticated user AlfrescoClientInfo alfClient = (AlfrescoClientInfo) client; - getAuthenticationComponent().setCurrentUser( mapUserNameToPerson( client.getUserName())); + getAuthenticationComponent().setCurrentUser( normalized); alfClient.setAuthenticationTicket(getAuthenticationService().getCurrentTicket()); // Store the full user name in the client information, indicate that this is not a guest logon @@ -2110,16 +2254,20 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement if (ex instanceof AuthenticationException) { - logger.debug(ex); + // Return a logon failure + logger.debug(ex.getMessage(), ex); + + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } else { - logger.error(ex); + logger.error(ex.getMessage(), ex); + + // Return a logon failure + + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } - // Return a logon failure - - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); } } else @@ -2130,7 +2278,7 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Return a logon failure - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } else @@ -2141,7 +2289,7 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Return a logon failure - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } @@ -2186,7 +2334,8 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Get the stored MD4 hashed password for the user, or null if the user does not exist - String md4hash = getNTLMAuthenticator().getMD4HashedPassword(userName); + String normalized = normalizeUserId(userName); + String md4hash = getNTLMAuthenticator().getMD4HashedPassword(normalized); if ( md4hash != null) { @@ -2217,8 +2366,6 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement } catch ( NoSuchAlgorithmException ex) { - // Log the error - logger.error( ex); } @@ -2256,9 +2403,8 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement if ( i != clientHash.length) { - // Return a logon failure - - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + logger.debug("bad client hash"); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrSrv, SMBStatus.SRVBadPassword); } } @@ -2267,12 +2413,14 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement try { AlfrescoClientInfo alfClient = (AlfrescoClientInfo) client; - getAuthenticationComponent().setCurrentUser( mapUserNameToPerson( userName)); + getAuthenticationComponent().setCurrentUser( normalized); alfClient.setAuthenticationTicket(getAuthenticationService().getCurrentTicket()); } catch (AuthenticationException e) { // Invalid user or max tickets exceeded. Return a logon failure status + + logger.debug("Authentication exception", e); throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } @@ -2294,7 +2442,7 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Return a logon failure - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } else @@ -2305,7 +2453,7 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Return a logon failure - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } @@ -2323,7 +2471,7 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement if (reqPkt.checkPacketIsValid(13, 0) == false) { - throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv); + throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.ErrSrv, SMBStatus.SRVNonSpecificError); } // Extract the session details @@ -2354,7 +2502,8 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement if (user == null) { - throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv); + logger.error("User not specified"); + throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.ErrSrv, SMBStatus.SRVNonSpecificError); } // Extract the clients primary domain name string @@ -2370,7 +2519,8 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement if (domain == null) { - throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv); + logger.error("Domain not specified"); + throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.ErrSrv, SMBStatus.SRVNonSpecificError); } } @@ -2387,13 +2537,14 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement if (clientOS == null) { - throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv); + logger.error("client OS not specified"); + throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.ErrSrv, SMBStatus.SRVNonSpecificError); } } // DEBUG - if (logger.isDebugEnabled() && sess.hasDebug(SMBSrvSession.DBG_NEGOTIATE)) + if (logger.isDebugEnabled()) { logger.debug("NT Session setup from user=" + user + ", password=" + (uniPwd != null ? HexDump.hexString(uniPwd) : "none") + ", ANSIpwd=" @@ -2441,7 +2592,9 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Debug if ( logger.isDebugEnabled()) + { logger.debug("Logged on using Hashed/NTLMv1"); + } } else if ( uniPwd.length > 0) { @@ -2452,7 +2605,9 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Debug if ( logger.isDebugEnabled()) + { logger.debug("Logged on using Hashed/NTLMv2"); + } } } @@ -2467,7 +2622,7 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // DEBUG - if (logger.isDebugEnabled() && sess.hasDebug(SMBSrvSession.DBG_NEGOTIATE)) + if (logger.isDebugEnabled()) logger.debug("User " + user + ", logged on as guest"); } @@ -2478,20 +2633,13 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement if ( uid == VirtualCircuit.InvalidUID) { - - // DEBUG + logger.error("Failed to allocate UID for virtual circuit, " + vc); - if ( logger.isDebugEnabled() && sess.hasDebug( SMBSrvSession.DBG_NEGOTIATE)) - logger.debug("Failed to allocate UID for virtual circuit, " + vc); - - // Failed to allocate a UID - - throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + // Failed to allocate a UID + throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } - else if ( logger.isDebugEnabled() && sess.hasDebug( SMBSrvSession.DBG_NEGOTIATE)) - { - // DEBUG - + else if ( logger.isDebugEnabled()) + { logger.debug("Allocated UID=" + uid + " for VC=" + vc); } diff --git a/source/java/org/alfresco/filesys/auth/cifs/PassthruCifsAuthenticator.java b/source/java/org/alfresco/filesys/auth/cifs/PassthruCifsAuthenticator.java index 84939fc8a4..d0d50c82e8 100644 --- a/source/java/org/alfresco/filesys/auth/cifs/PassthruCifsAuthenticator.java +++ b/source/java/org/alfresco/filesys/auth/cifs/PassthruCifsAuthenticator.java @@ -77,7 +77,7 @@ public class PassthruCifsAuthenticator extends CifsAuthenticatorBase implements { // Debug logging - private static final Log logger = LogFactory.getLog("org.alfresco.smb.protocol.auth"); + private static final Log logger = LogFactory.getLog(PassthruCifsAuthenticator.class); // Constants @@ -465,6 +465,58 @@ public class PassthruCifsAuthenticator extends CifsAuthenticatorBase implements respPkt.setByteCount(pos - respPkt.getByteOffset()); } + /** + * Process the CIFS session setup request packet and build the session setup response. + *

+ * This is the boundary between alfresco and JLAN. So is responsible for logging and + * ensuring that the exceptions are correct for JLAN. + *

+ * @param sess SMBSrvSession + * @param reqPkt SMBSrvPacket + * @exception SMBSrvException + */ + public void processSessionSetup(final SMBSrvSession sess, final SMBSrvPacket reqPkt) + throws SMBSrvException + { + try + { + processAlfrescoSessionSetup(sess, reqPkt); + } + catch (SMBSrvException e) + { + /* + * A JLAN SMBSrvException is not human readable so we need to log the + * error before throwing, rather than logging the error here. + */ + if(logger.isDebugEnabled()) + { + logger.debug("Returning SMBSrvException to JLAN", e); + } + throw e; + } + catch (AlfrescoRuntimeException a) + { + Throwable c = a.getCause(); + + if(c != null) + { + if(a.getCause() instanceof SMBSrvException) + { + logger.error(c.getMessage(), c); + throw (SMBSrvException)c; + } + } + logger.error(a.getMessage(), a); + throw new SMBSrvException( SMBStatus.NTAccessDenied, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); + + } + catch (Throwable t) + { + logger.error(t.getMessage(), t); + throw new SMBSrvException( SMBStatus.NTAccessDenied, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); + } + } + /** * Process the CIFS session setup request packet and build the session setup response * @@ -472,14 +524,20 @@ public class PassthruCifsAuthenticator extends CifsAuthenticatorBase implements * @param reqPkt SMBSrvPacket * @exception SMBSrvException */ - public void processSessionSetup(SMBSrvSession sess, SMBSrvPacket reqPkt) + public void processAlfrescoSessionSetup(SMBSrvSession sess, SMBSrvPacket reqPkt) throws SMBSrvException { // Check that the received packet looks like a valid NT session setup andX request if (reqPkt.checkPacketIsValid(12, 0) == false) - throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv); - + { + if(logger.isErrorEnabled()) + { + logger.error("Invalid packet received, return SMBStatus.NTInvalidParameter"); + } + throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.ErrSrv, SMBStatus.SRVNonSpecificError); + } + // Check if the request is using security blobs or the older hashed password format if ( reqPkt.getParameterCount() == 13) @@ -518,28 +576,42 @@ public class PassthruCifsAuthenticator extends CifsAuthenticatorBase implements String domain = ""; - if (reqPkt.hasMoreData()) { + if (reqPkt.hasMoreData()) + { // Extract the callers domain name domain = reqPkt.unpackString(isUni); if (domain == null) - throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv); + { + if(logger.isErrorEnabled()) + { + logger.error("Invalid packet received, domain is null"); + } + throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.ErrSrv, SMBStatus.SRVNonSpecificError); + } } // Extract the clients native operating system String clientOS = ""; - if (reqPkt.hasMoreData()) { + if (reqPkt.hasMoreData()) + { // Extract the callers operating system name clientOS = reqPkt.unpackString(isUni); if (clientOS == null) - throw new SMBSrvException( SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv); + { + if(logger.isErrorEnabled()) + { + logger.error("Invalid packet received, client OS is null"); + } + throw new SMBSrvException( SMBStatus.NTInvalidParameter, SMBStatus.ErrSrv, SMBStatus.SRVNonSpecificError); + } } // Store the client maximum buffer size, maximum multiplexed requests count and client capability flags @@ -559,8 +631,9 @@ public class PassthruCifsAuthenticator extends CifsAuthenticatorBase implements // Set the remote address, if available if ( sess.hasRemoteAddress()) - client.setClientAddress(sess.getRemoteAddress().getHostAddress()); - + { + client.setClientAddress(sess.getRemoteAddress().getHostAddress()); + } // Set the process id for this client, for multi-stage logons client.setProcessId( reqPkt.getProcessId()); @@ -598,8 +671,9 @@ public class PassthruCifsAuthenticator extends CifsAuthenticatorBase implements // DEBUG if (logger.isDebugEnabled()) - logger.debug("NT Session setup NTLMSSP, MID=" + reqPkt.getMultiplexId() + ", UID=" + reqPkt.getUserId() + ", PID=" + reqPkt.getProcessId()); - + { + logger.debug("NT Session setup NTLMSSP, MID=" + reqPkt.getMultiplexId() + ", UID=" + reqPkt.getUserId() + ", PID=" + reqPkt.getProcessId()); + } // Process an NTLMSSP security blob respBlob = doNtlmsspSessionSetup( sess, client, buf, secBlobPos, secBlobLen, isUni); @@ -608,7 +682,12 @@ public class PassthruCifsAuthenticator extends CifsAuthenticatorBase implements { // Invalid blob type - throw new SMBSrvException( SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv); + if(logger.isErrorEnabled()) + { + logger.error("Invalid packet received for Passthru Cifs Autenticator, not of type NTLMSSP"); + } + + throw new SMBSrvException( SMBStatus.NTInvalidParameter, SMBStatus.ErrSrv, SMBStatus.SRVNonSpecificError); } } catch (SMBSrvException ex) @@ -624,9 +703,11 @@ public class PassthruCifsAuthenticator extends CifsAuthenticatorBase implements // Debug - if ( logger.isDebugEnabled() && sess.hasDebug(SMBSrvSession.DBG_NEGOTIATE)) + if ( logger.isDebugEnabled()) + { logger.debug("User " + client.getUserName() + " logged on " + (client != null ? " (type " + client.getLogonTypeString() + ")" : "")); - + } + // Update the client information if not already set if ( sess.getClientInformation() == null || @@ -719,17 +800,14 @@ public class PassthruCifsAuthenticator extends CifsAuthenticatorBase implements uid = sess.addVirtualCircuit( vc); if ( uid == VirtualCircuit.InvalidUID) - { - // DEBUG - - if ( logger.isDebugEnabled() && sess.hasDebug( SMBSrvSession.DBG_NEGOTIATE)) - logger.debug("Failed to allocate UID for virtual circuit, " + vc); + { + logger.error("Failed to allocate UID for virtual circuit, " + vc); // Failed to allocate a UID - - throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } - else if ( logger.isDebugEnabled() && sess.hasDebug( SMBSrvSession.DBG_NEGOTIATE)) { + else if ( logger.isDebugEnabled()) + { // DEBUG @@ -800,12 +878,9 @@ public class PassthruCifsAuthenticator extends CifsAuthenticatorBase implements if ( msgType == -1) { - // DEBUG - - if ( logger.isDebugEnabled()) + if ( logger.isErrorEnabled()) { - logger.debug("Invalid NTLMSSP token received"); - logger.debug(" Token=" + HexDump.hexString( secbuf, secpos, seclen, " ")); + logger.error("Invalid NTLMSSP token received, Token=" + HexDump.hexString( secbuf, secpos, seclen, " ") ); } // Return a logon failure status @@ -875,22 +950,20 @@ public class PassthruCifsAuthenticator extends CifsAuthenticatorBase implements sess.removeSetupObject( client.getProcessId()); // Return a logon failure - - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + logger.error("NTLMSSP Logon failure - type 2 message not found"); + + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } // Determine if the client sent us NTLMv1 or NTLMv2 if ( type3Msg.hasFlag( NTLM.Flag128Bit) && type3Msg.hasFlag( NTLM.FlagNTLM2Key)) { - // Debug - - if ( logger.isDebugEnabled()) - logger.debug("Received NTLMSSP/NTLMv2, not supported"); + logger.error("Received NTLMSSP/NTLMv2, not supported"); // Return a logon failure - throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } else { @@ -1042,10 +1115,12 @@ public class PassthruCifsAuthenticator extends CifsAuthenticatorBase implements } catch (Exception ex) { + if(ex instanceof SMBSrvException) + { + throw (SMBSrvException)ex; + } - // Debug - - logger.error(ex.getMessage()); + logger.error("unable to log on "+ ex.getMessage(), ex); // Indicate logon failure @@ -1067,27 +1142,21 @@ public class PassthruCifsAuthenticator extends CifsAuthenticatorBase implements AuthenticateSession authSess = passDetails.getAuthenticateSession(); authSess.CloseSession(); - // DEBUG - if (logger.isDebugEnabled()) + { logger.debug("Closed auth session, sessId=" + authSess.getSessionId()); + } } catch (Exception ex) { - // Debug - logger.error("Passthru error closing session (auth user)", ex); } } } else { - - // DEBUG - - if (logger.isDebugEnabled()) - logger.debug(" No PassthruDetails for " + sess.getUniqueId() + ", check server list/domain mappings"); + logger.error(" No PassthruDetails for " + sess.getUniqueId() + ", check server list/domain mappings"); // Indicate logon failure diff --git a/source/java/org/alfresco/filesys/auth/cifs/package-info.java b/source/java/org/alfresco/filesys/auth/cifs/package-info.java index b5e6184554..725016bc23 100644 --- a/source/java/org/alfresco/filesys/auth/cifs/package-info.java +++ b/source/java/org/alfresco/filesys/auth/cifs/package-info.java @@ -1,3 +1,29 @@ +/* + * Copyright (C) 2005-2012 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ /** + * Provides authentication implementations for CIFS + *

+ * AlfrescoCifsAuthenticator + * EnterpriseCifsAuthenticator deals with Kerberos, NTLMv1 and NTLMv2 + * PassthruCifsAuthenticator deals with authenticating against an external system + * + *

+ * CifsAuthenticatorBase abstract base class. */ package org.alfresco.filesys.auth.cifs; diff --git a/source/java/org/alfresco/filesys/avm/AVMDiskDriver.java b/source/java/org/alfresco/filesys/avm/AVMDiskDriver.java index f3f7923d1a..6011e7f23c 100644 --- a/source/java/org/alfresco/filesys/avm/AVMDiskDriver.java +++ b/source/java/org/alfresco/filesys/avm/AVMDiskDriver.java @@ -2027,7 +2027,7 @@ public class AVMDiskDriver extends AlfrescoTxDiskDriver implements DiskInterface { // Check if the file is a directory, or only has read access - if (file.getGrantedAccess() == NetworkFile.READONLY) + if (file.getGrantedAccess() <= NetworkFile.READONLY) throw new AccessDeniedException(); // If the content channel is not open for the file then start a transaction @@ -2081,7 +2081,7 @@ public class AVMDiskDriver extends AlfrescoTxDiskDriver implements DiskInterface { // Check if the file is a directory, or only has read access - if (file.isDirectory() || file.getGrantedAccess() == NetworkFile.READONLY) + if (file.isDirectory() || file.getGrantedAccess() <= NetworkFile.READONLY) throw new AccessDeniedException(); // If the content channel is not open for the file, or the channel is not writable, then start a transaction diff --git a/source/java/org/alfresco/filesys/avm/AVMNetworkFile.java b/source/java/org/alfresco/filesys/avm/AVMNetworkFile.java index 1f57488937..800922534d 100644 --- a/source/java/org/alfresco/filesys/avm/AVMNetworkFile.java +++ b/source/java/org/alfresco/filesys/avm/AVMNetworkFile.java @@ -462,7 +462,7 @@ public class AVMNetworkFile extends AlfrescoNetworkFile { // We need to create the channel - if (write && getGrantedAccess() == NetworkFile.READONLY) + if (write && getGrantedAccess() <= NetworkFile.READONLY) throw new AccessDeniedException("The network file was created for read-only: " + this); // Access the content data and get a file channel to the data diff --git a/source/java/org/alfresco/filesys/repo/ContentDiskDriver.java b/source/java/org/alfresco/filesys/repo/ContentDiskDriver.java index d98c2d67d0..cb3eb2a2e8 100644 --- a/source/java/org/alfresco/filesys/repo/ContentDiskDriver.java +++ b/source/java/org/alfresco/filesys/repo/ContentDiskDriver.java @@ -1027,8 +1027,8 @@ public class ContentDiskDriver extends AlfrescoTxDiskDriver implements DiskInter // Copy cached timestamps - if ( fstate.hasAccessDateTime()) - finfo.setAccessDateTime(fstate.getAccessDateTime()); +// if ( fstate.hasAccessDateTime()) +// finfo.setAccessDateTime(fstate.getAccessDateTime()); if ( fstate.hasChangeDateTime()) finfo.setChangeDateTime(fstate.getChangeDateTime()); if ( fstate.hasModifyDateTime()) @@ -1731,7 +1731,7 @@ public class ContentDiskDriver extends AlfrescoTxDiskDriver implements DiskInter throw new AccessDeniedException("Invalid access mode"); } - if ( fstate.getOpenCount() > 0) { + if ( fstate.getOpenCount() > 0 && params.isAttributesOnlyAccess() == false) { // Check for impersonation security level from the original process that opened the file @@ -1819,7 +1819,7 @@ public class ContentDiskDriver extends AlfrescoTxDiskDriver implements DiskInter { // Check if the file is already opened by this client/process - if ( tree.openFileCount() > 0) { + if ( tree.openFileCount() > 0 && params.isAttributesOnlyAccess() == false) { // Search the open file table for this session/virtual circuit @@ -1856,7 +1856,7 @@ public class ContentDiskDriver extends AlfrescoTxDiskDriver implements DiskInter 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")); + ( 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") + @@ -1877,7 +1877,7 @@ public class ContentDiskDriver extends AlfrescoTxDiskDriver implements DiskInter // Create a new network file for the open request - netFile = ContentNetworkFile.createFile(nodeService, contentService, mimetypeService, cifsHelper, nodeRef, params.getPath(), params.isReadOnlyAccess(), sess); + netFile = ContentNetworkFile.createFile(nodeService, contentService, mimetypeService, cifsHelper, nodeRef, params.getPath(), params.isReadOnlyAccess(), params.isAttributesOnlyAccess(), sess); } } else @@ -1961,7 +1961,8 @@ public class ContentDiskDriver extends AlfrescoTxDiskDriver implements DiskInter // Update the file state, cache the node - fstate.incrementOpenCount(); + if ( netFile.getGrantedAccess() > NetworkFile.ATTRIBUTESONLY) + fstate.incrementOpenCount(); fstate.setFilesystemObject(nodeRef); // Store the state with the file @@ -1970,8 +1971,8 @@ public class ContentDiskDriver extends AlfrescoTxDiskDriver implements DiskInter // Set the file access date/time, if available - if ( fstate.hasAccessDateTime()) - netFile.setAccessDate( fstate.getAccessDateTime()); +// if ( fstate.hasAccessDateTime()) +// netFile.setAccessDate( fstate.getAccessDateTime()); } // Debug @@ -2090,7 +2091,7 @@ public class ContentDiskDriver extends AlfrescoTxDiskDriver implements DiskInter // Create the network file - ContentNetworkFile netFile = ContentNetworkFile.createFile(nodeService, contentService, mimetypeService, cifsHelper, result.getSecond(), params.getPath(), params.isReadOnlyAccess(), sess); + ContentNetworkFile netFile = ContentNetworkFile.createFile(nodeService, contentService, mimetypeService, cifsHelper, result.getSecond(), params.getPath(), params.isReadOnlyAccess(), params.isAttributesOnlyAccess(), sess); // Always allow write access to a newly created file @@ -2480,7 +2481,7 @@ public class ContentDiskDriver extends AlfrescoTxDiskDriver implements DiskInter // If the file open count is now zero then reset the stored sharing mode - if ( fstate.decrementOpenCount() == 0) + if ( file.getGrantedAccess() > NetworkFile.ATTRIBUTESONLY && fstate.decrementOpenCount() == 0) fstate.setSharedAccess( SharingMode.READWRITE + SharingMode.DELETE); // Check if there is a cached modification timestamp to be written out @@ -3433,11 +3434,9 @@ public class ContentDiskDriver extends AlfrescoTxDiskDriver implements DiskInter } catch (AlfrescoRuntimeException ex) { - // Debug - - if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME)) - logger.debug("Rename file", ex); - + // Unexpected Exception being consumed here - hence the error logging. + logger.error("Unable to rename file" + oldName, ex); + // Convert to a general I/O exception throw new AccessDeniedException("Rename file " + oldName); diff --git a/source/java/org/alfresco/filesys/repo/ContentDiskDriver2.java b/source/java/org/alfresco/filesys/repo/ContentDiskDriver2.java index 41b86929ec..52343746da 100644 --- a/source/java/org/alfresco/filesys/repo/ContentDiskDriver2.java +++ b/source/java/org/alfresco/filesys/repo/ContentDiskDriver2.java @@ -2100,6 +2100,11 @@ public class ContentDiskDriver2 extends AlfrescoDiskDriver implements ExtendedD diskDev.setTotalUnits( totalSpace / DiskSizeInterfaceConsts.DiskAllocationUnit); diskDev.setFreeUnits( freeSpace / DiskSizeInterfaceConsts.DiskAllocationUnit); + + if(logger.isDebugEnabled()) + { + logger.debug("getDiskInformation returning diskDev:" + diskDev); + } } public void setCifsHelper(CifsHelper cifsHelper) @@ -2145,16 +2150,6 @@ public class ContentDiskDriver2 extends AlfrescoDiskDriver implements ExtendedD logger.debug("processIOControl ctrlCode: 0x" + Integer.toHexString(ctrlCode) + ", fid:" + fid); } - NetworkFile netFile = tree.findFile(fid); - if ( netFile == null || netFile.isDirectory() == false) - { - if(logger.isDebugEnabled()) - { - logger.debug("net file is null or not a directory"); - } - throw new SMBException(SMBStatus.NTErr, SMBStatus.NTInvalidParameter); - } - final ContentContext ctx = (ContentContext) tree.getContext(); try { @@ -2170,7 +2165,14 @@ public class ContentDiskDriver2 extends AlfrescoDiskDriver implements ExtendedD } throw smbException; } - + catch(IOControlNotImplementedException ioException) + { + if(logger.isDebugEnabled()) + { + logger.debug("IO Control Not Implemented Exception fid:" + fid, ioException); + } + throw ioException; + } } @@ -2446,7 +2448,7 @@ public class ContentDiskDriver2 extends AlfrescoDiskDriver implements ExtendedD case READ_ONLY: logger.debug("open file for read only"); - netFile = ContentNetworkFile.createFile(nodeService, contentService, mimetypeService, getCifsHelper(), nodeRef, path, true, session); + netFile = ContentNetworkFile.createFile(nodeService, contentService, mimetypeService, getCifsHelper(), nodeRef, path, true, false, session); netFile.setGrantedAccess( NetworkFile.READONLY); break; @@ -2484,14 +2486,14 @@ public class ContentDiskDriver2 extends AlfrescoDiskDriver implements ExtendedD case ATTRIBUTES_ONLY: logger.debug("open file for attributes only"); - netFile = ContentNetworkFile.createFile(nodeService, contentService, mimetypeService, getCifsHelper(), nodeRef, path, true, session); + netFile = ContentNetworkFile.createFile(nodeService, contentService, mimetypeService, getCifsHelper(), nodeRef, path, true, true, session); netFile.setGrantedAccess( NetworkFile.READONLY); break; case DELETE: //TODO Not sure about this one. logger.debug("open file for delete"); - netFile = ContentNetworkFile.createFile(nodeService, contentService, mimetypeService, getCifsHelper(), nodeRef, path,true , session); + netFile = ContentNetworkFile.createFile(nodeService, contentService, mimetypeService, getCifsHelper(), nodeRef, path, true, false, session); netFile.setGrantedAccess( NetworkFile.READONLY); break; @@ -2551,7 +2553,7 @@ public class ContentDiskDriver2 extends AlfrescoDiskDriver implements ExtendedD urlStr.append( srvName); urlStr.append("/"); urlStr.append( tree.getSharedDevice().getName()); - urlStr.append( pathl); + urlStr.append( path); urlStr.append("\r\n"); // Create the in memory pseudo file for the URL link diff --git a/source/java/org/alfresco/filesys/repo/ContentDiskDriverTest.java b/source/java/org/alfresco/filesys/repo/ContentDiskDriverTest.java index e820644f99..dffcaf542d 100644 --- a/source/java/org/alfresco/filesys/repo/ContentDiskDriverTest.java +++ b/source/java/org/alfresco/filesys/repo/ContentDiskDriverTest.java @@ -2467,6 +2467,252 @@ public class ContentDiskDriverTest extends TestCase } // testScenarioShuffleMetadataExtraction + + /** + * ALF-12812 + * + * This test tries to simulate the shuffling that is done by MS Word 2011 for Mac + * with regard to metadata extraction. In particular the temporary file names are + * different. + *

+ * 1: Setup an update rule for ContentMetadataExtractor. + * Simulate a WORD 2011 for Mac Create + * 2: Write "Word Work File D_1725484373.tmp" + * 3: Close file + * 4: Rename "Word Work File D_1725484373.tmp" to ContentDiskDriver.docx + * 5: Check metadata extraction + */ + public void testMetadataExtractionForMac() throws Exception + { + logger.debug("testMetadataExtractionForMac"); + final String FILE_NAME = "ContentDiskDriver.docx"; + //final String FILE_OLD_TEMP = "._Word Work File D_1725484373.tmp"; + final String FILE_NEW_TEMP = "Word Work File D_1725484373.tmp"; + + class TestContext + { + NodeRef testDirNodeRef; + NodeRef testNodeRef; + NetworkFile firstFileHandle; +// NetworkFile secondFileHandle; + }; + + final TestContext testContext = new TestContext(); + + final String TEST_DIR = TEST_ROOT_DOS_PATH + "\\testMetadataExtractionForMac"; + + ServerConfiguration scfg = new ServerConfiguration("testServer"); + TestServer testServer = new TestServer("testServer", scfg); + final SrvSession testSession = new TestSrvSession(666, testServer, "test", "remoteName"); + DiskSharedDevice share = getDiskSharedDevice(); + final TreeConnection testConnection = testServer.getTreeConnection(share); + final RetryingTransactionHelper tran = transactionService.getRetryingTransactionHelper(); + + /** + * Clean up just in case garbage is left from a previous run + */ + RetryingTransactionCallback deleteGarbageDirCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + driver.deleteDirectory(testSession, testConnection, TEST_DIR); + return null; + } + }; + + try + { + tran.doInTransaction(deleteGarbageDirCB); + } + catch (Exception e) + { + // expect to go here + } + + logger.debug("create Test directory" + TEST_DIR); + RetryingTransactionCallback createTestDirCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + /** + * Create the test directory we are going to use + */ + FileOpenParams createRootDirParams = new FileOpenParams(TEST_ROOT_DOS_PATH, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + FileOpenParams createDirParams = new FileOpenParams(TEST_DIR, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + driver.createDirectory(testSession, testConnection, createRootDirParams); + driver.createDirectory(testSession, testConnection, createDirParams); + + testContext.testDirNodeRef = getNodeForPath(testConnection, TEST_DIR); + assertNotNull("testDirNodeRef is null", testContext.testDirNodeRef); + + UserTransaction txn = transactionService.getUserTransaction(); + + return null; + + + } + }; + tran.doInTransaction(createTestDirCB); + logger.debug("Create rule on test dir"); + + RetryingTransactionCallback createRuleCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + Rule rule = new Rule(); + rule.setRuleType(RuleType.UPDATE); + rule.applyToChildren(true); + rule.setRuleDisabled(false); + rule.setTitle("Extract Metadata from update content"); + rule.setDescription("ContentDiskDriverTest"); + + Map props = new HashMap(1); + Action extractAction = actionService.createAction("extract-metadata", props); + + ActionCondition noCondition1 = actionService.createActionCondition(NoConditionEvaluator.NAME); + extractAction.addActionCondition(noCondition1); + + ActionCondition noCondition2 = actionService.createActionCondition(NoConditionEvaluator.NAME); + CompositeAction compAction = actionService.createCompositeAction(); + compAction.setTitle("Extract Metadata"); + compAction.setDescription("Content Disk Driver Test - Extract Metadata"); + compAction.addAction(extractAction); + compAction.addActionCondition(noCondition2); + + rule.setAction(compAction); + + ruleService.saveRule(testContext.testDirNodeRef, rule); + + logger.debug("rule created"); + + return null; + } + }; + tran.doInTransaction(createRuleCB, false, true); + + /** + * Create a file in the test directory + */ + logger.debug("create test file in test directory"); + RetryingTransactionCallback createFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + /** + * Create the file we are going to use to test + */ + FileOpenParams createFileParams = new FileOpenParams(TEST_DIR + "\\" + FILE_NEW_TEMP, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + testContext.firstFileHandle = driver.createFile(testSession, testConnection, createFileParams); + assertNotNull("first file Handle is null", testContext.firstFileHandle); + + // now load up the node with lots of other stuff that we will test to see if it gets preserved during the + // shuffle. + testContext.testNodeRef = getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NEW_TEMP); + assertNotNull("testContext.testNodeRef is null", testContext.testNodeRef); + + // test non CM namespace property + nodeService.setProperty(testContext.testNodeRef, TransferModel.PROP_ENABLED, true); + + // Check that the temporary aspect has been applied. + assertTrue("temporary aspect not applied", nodeService.hasAspect(testContext.testNodeRef, ContentModel.ASPECT_TEMPORARY)); + return null; + } + }; + tran.doInTransaction(createFileCB, false, true); + + logger.debug("step b: write content to test file"); + + /** + * Write ContentDiskDriverTest1.docx to the test file, + */ + RetryingTransactionCallback writeFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + ClassPathResource fileResource = new ClassPathResource("filesys/ContentDiskDriverTest1.docx"); + assertNotNull("unable to find test resource filesys/ContentDiskDriverTest1.docx", fileResource); + + byte[] buffer= new byte[1000]; + InputStream is = fileResource.getInputStream(); + try + { + long offset = 0; + int i = is.read(buffer, 0, buffer.length); + while(i > 0) + { + testContext.firstFileHandle.writeFile(buffer, i, 0, offset); + offset += i; + i = is.read(buffer, 0, buffer.length); + } + } + finally + { + is.close(); + } + + driver.closeFile(testSession, testConnection, testContext.firstFileHandle); + + return null; + } + }; + tran.doInTransaction(writeFileCB, false, true); + + logger.debug("Step b: rename the test file."); + + /** + * Move the new file into place, stuff should get shuffled + */ + RetryingTransactionCallback moveNewFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + driver.renameFile(testSession, testConnection, TEST_DIR + "\\" + FILE_NEW_TEMP, TEST_DIR + "\\" + FILE_NAME); + return null; + } + }; + + tran.doInTransaction(moveNewFileCB, false, true); + + logger.debug("Step c: validate metadata has been extracted."); + /** + * c: check simple case of meta-data extraction has worked. + */ + RetryingTransactionCallback validateFirstExtractionCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + Map props = nodeService.getProperties(testContext.testNodeRef); + + assertTrue("Enabled property has been lost", props.containsKey(TransferModel.PROP_ENABLED)); + + // Check that the temporary aspect has been applied. + assertTrue("temporary aspect has not been removed", !nodeService.hasAspect(testContext.testNodeRef, ContentModel.ASPECT_TEMPORARY)); + + + // These metadata values should be extracted. + assertEquals("description is not correct", "This is a test file", nodeService.getProperty(testContext.testNodeRef, ContentModel.PROP_DESCRIPTION)); + assertEquals("title is not correct", "ContentDiskDriverTest", nodeService.getProperty(testContext.testNodeRef, ContentModel.PROP_TITLE)); + assertEquals("author is not correct", "mrogers", nodeService.getProperty(testContext.testNodeRef, ContentModel.PROP_AUTHOR)); + + ContentData data = (ContentData)props.get(ContentModel.PROP_CONTENT); + assertEquals("mimeType is wrong", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", data.getMimetype()); + assertEquals("size is wrong", 11302, data.getSize()); + + return null; + } + }; + tran.doInTransaction(validateFirstExtractionCB, false, true); + + + } // testScenarioMetadataExtractionForMac + public void testDirListing()throws Exception { logger.debug("testDirListing"); @@ -4352,7 +4598,7 @@ public class ContentDiskDriverTest extends TestCase * e) Lock file deleted * */ - public void testScenarioMacLionTextEdit() throws Exception + public void DISABLED_TestScenarioMacLionTextEdit() throws Exception { logger.debug("testScenarioLionTextEdit"); final String FILE_NAME = "test.txt"; diff --git a/source/java/org/alfresco/filesys/repo/ContentIOControlHandler.java b/source/java/org/alfresco/filesys/repo/ContentIOControlHandler.java index 29df048bac..6c80f5d8b7 100644 --- a/source/java/org/alfresco/filesys/repo/ContentIOControlHandler.java +++ b/source/java/org/alfresco/filesys/repo/ContentIOControlHandler.java @@ -116,7 +116,8 @@ public class ContentIOControlHandler implements IOControlHandler * @param dataBuf I/O control specific input data * @param isFSCtrl true if this is a filesystem control, or false for a device control * @param filter if bit0 is set indicates that the control applies to the share root handle - * @return DataBuffer + * @return DataBuffer or null if there is no response buffer. + * * @exception IOControlNotImplementedException * @exception SMBException */ @@ -125,10 +126,12 @@ public class ContentIOControlHandler implements IOControlHandler throws IOControlNotImplementedException, SMBException { // Validate the file id - NetworkFile netFile = tree.findFile(fid); - if ( netFile == null || netFile.isDirectory() == false) + if ( netFile == null ) + { + logger.debug("IO Control Handler called with missing file"); throw new SMBException(SMBStatus.NTErr, SMBStatus.NTInvalidParameter); + } // Split the control code @@ -144,23 +147,36 @@ public class ContentIOControlHandler implements IOControlHandler // Create or get object id if ( ioFunc == NTIOCtl.FsCtlCreateOrGetObjectId) - return null; + { + logger.debug("Create or Get Object Id - return null"); + return null; +// +// logger.debug("Create or Get Object Id - throw not implemented exception"); +// throw new IOControlNotImplementedException("Create or Get Object Id not implemented"); +// //return null; + } } // Check if the I/O control looks like a custom I/O control request if ( devType != NTIOCtl.DeviceFileSystem || dataBuf == null) - throw new IOControlNotImplementedException(); + { + throw new IOControlNotImplementedException("Custom IO control request not implemented"); + } // Check if the request has a valid signature for an Alfresco CIFS server I/O control if ( dataBuf.getLength() < IOControl.Signature.length()) + { throw new IOControlNotImplementedException("Bad request length"); + } String sig = dataBuf.getFixedString(IOControl.Signature.length(), false); if ( sig == null || sig.compareTo(IOControl.Signature) != 0) + { throw new IOControlNotImplementedException("Bad request signature"); + } // Get the node for the parent folder, make sure it is a folder @@ -171,7 +187,9 @@ public class ContentIOControlHandler implements IOControlHandler folderNode = getNodeForPath(tree, netFile.getFullName()); if ( getCifsHelper().isDirectory( folderNode) == false) + { folderNode = null; + } } catch ( FileNotFoundException ex) { @@ -181,14 +199,17 @@ public class ContentIOControlHandler implements IOControlHandler // If the folder node is not valid return an error if ( folderNode == null) + { + logger.debug("unable to get parent folder - return access denied"); throw new SMBException(SMBStatus.NTErr, SMBStatus.NTAccessDenied); + } // Debug if ( logger.isDebugEnabled()) { - logger.debug("IO control func=0x" + Integer.toHexString(ioFunc) + ", fid=" + fid + ", buffer=" + dataBuf); - logger.debug(" Folder nodeRef=" + folderNode); + logger.debug("IO control func=0x" + Integer.toHexString(ioFunc) + ", fid=" + fid + ", buffer=" + dataBuf + + " Folder nodeRef=" + folderNode); } // Check if the I/O control code is one of our custom codes @@ -217,8 +238,11 @@ public class ContentIOControlHandler implements IOControlHandler // Get file information for a file within the current folder case IOControl.CmdFileStatus: - - + + if(logger.isDebugEnabled()) + { + logger.debug("CmdFileStatus"); + } // Process the file status request retBuffer = procIOFileStatus( sess, tree, dataBuf, folderNode, contentDriver, contentContext); @@ -229,6 +253,10 @@ public class ContentIOControlHandler implements IOControlHandler case IOControl.CmdGetActionInfo: // Process the get action information request + if(logger.isDebugEnabled()) + { + logger.debug("GetActionInfo"); + } retBuffer = procGetActionInfo(sess, tree, dataBuf, folderNode, netFile, contentDriver, contentContext); break; @@ -238,6 +266,10 @@ public class ContentIOControlHandler implements IOControlHandler case IOControl.CmdRunAction: // Process the run action request + if(logger.isDebugEnabled()) + { + logger.debug("RunAction"); + } retBuffer = procRunAction(sess, tree, dataBuf, folderNode, netFile, contentDriver, contentContext); break; @@ -247,6 +279,10 @@ public class ContentIOControlHandler implements IOControlHandler case IOControl.CmdGetAuthTicket: // Process the get auth ticket request + if(logger.isDebugEnabled()) + { + logger.debug("GetAuthTicket"); + } retBuffer = procGetAuthTicket(sess, tree, dataBuf, folderNode, netFile, contentDriver, contentContext); break; diff --git a/source/java/org/alfresco/filesys/repo/ContentNetworkFile.java b/source/java/org/alfresco/filesys/repo/ContentNetworkFile.java index 70ff97c51d..602c559700 100644 --- a/source/java/org/alfresco/filesys/repo/ContentNetworkFile.java +++ b/source/java/org/alfresco/filesys/repo/ContentNetworkFile.java @@ -88,7 +88,7 @@ public class ContentNetworkFile extends NodeRefNetworkFile * Helper method to create a {@link NetworkFile network file} given a node reference. */ public static ContentNetworkFile createFile( NodeService nodeService, ContentService contentService, MimetypeService mimetypeService, - CifsHelper cifsHelper, NodeRef nodeRef, String path, boolean readOnly, SrvSession sess) + CifsHelper cifsHelper, NodeRef nodeRef, String path, boolean readOnly, boolean attributesOnly, SrvSession sess) { // Create the file @@ -116,12 +116,13 @@ public class ContentNetworkFile extends NodeRefNetworkFile // Set relevant parameters - if (readOnly) - { + if (attributesOnly) { + netFile.setGrantedAccess( NetworkFile.ATTRIBUTESONLY); + } + else if (readOnly) { netFile.setGrantedAccess(NetworkFile.READONLY); } - else - { + else { netFile.setGrantedAccess(NetworkFile.READWRITE); } @@ -153,11 +154,15 @@ public class ContentNetworkFile extends NodeRefNetworkFile if ( fileInfo.hasCreationDateTime()) netFile.setCreationDate( fileInfo.getCreationDateTime()); - if ( fileInfo.hasModifyDateTime()) + if ( fileInfo.hasModifyDateTime() && fileInfo.getModifyDateTime() > 0L) netFile.setModifyDate(fileInfo.getModifyDateTime()); + else + netFile.setModifyDate(fileInfo.getCreationDateTime()); - if ( fileInfo.hasAccessDateTime()) + if ( fileInfo.hasAccessDateTime() && fileInfo.getAccessDateTime() > 0L) netFile.setAccessDate(fileInfo.getAccessDateTime()); + else + netFile.setAccessDate(fileInfo.getCreationDateTime()); // Set the file attributes diff --git a/source/java/org/alfresco/filesys/repo/ContentSearchContext.java b/source/java/org/alfresco/filesys/repo/ContentSearchContext.java index 8d12657ad1..595107dbc8 100644 --- a/source/java/org/alfresco/filesys/repo/ContentSearchContext.java +++ b/source/java/org/alfresco/filesys/repo/ContentSearchContext.java @@ -42,7 +42,8 @@ import org.apache.commons.logging.LogFactory; * * @author Derek Hulley */ -public class ContentSearchContext extends SearchContext +public class ContentSearchContext extends SearchContext + implements InFlightCorrectable { // Debug logging @@ -54,6 +55,13 @@ public class ContentSearchContext extends SearchContext public final static int LinkFileSize = 512; + private InFlightCorrector corrector; + + public void setInFlightCorrector(InFlightCorrector corrector) + { + this.corrector = corrector; + } + // List of nodes returned from the folder search private CifsHelper cifsHelper; @@ -163,7 +171,9 @@ public class ContentSearchContext extends SearchContext // Check if there is anything else to return if (!hasMoreFiles()) + { return false; + } // Increment the index and resume id @@ -231,17 +241,28 @@ public class ContentSearchContext extends SearchContext try { - // Get the file information and copy across to the callers file info + // Get the file information and copy across to the caller's file info nextInfo = cifsHelper.getFileInformation(nextNodeRef, "", false, false); info.copyFrom(nextInfo); + + /** + * Apply in flight correction + */ + if(corrector != null) + { + corrector.correct(info, m_relPath); + } + } catch ( InvalidNodeRefException ex) { // Log a warning if ( logger.isWarnEnabled()) + { logger.warn("Noderef " + nextNodeRef + " no longer valid, ignoring"); + } // Update the node index, node no longer exists, try the next node in the search @@ -253,7 +274,9 @@ public class ContentSearchContext extends SearchContext // Check if we have finished returning file info if ( nextInfo == null) + { return false; + } // Generate a file id for the current file diff --git a/source/java/org/alfresco/filesys/repo/FilesystemTransactionAdvice.java b/source/java/org/alfresco/filesys/repo/FilesystemTransactionAdvice.java index 2f36ae4111..ca934eab72 100644 --- a/source/java/org/alfresco/filesys/repo/FilesystemTransactionAdvice.java +++ b/source/java/org/alfresco/filesys/repo/FilesystemTransactionAdvice.java @@ -45,8 +45,6 @@ public class FilesystemTransactionAdvice implements MethodInterceptor { private boolean readOnly; -// private AlfrescoDiskDriver driver; - private TransactionService transactionService; public FilesystemTransactionAdvice() @@ -92,64 +90,34 @@ public class FilesystemTransactionAdvice implements MethodInterceptor } }; - if(readOnly) + try { - // read only transaction - try - { - return tran.doInTransaction(callback, true); - } - catch(PropagatingException pe) - { - Throwable t = pe.getCause(); - if(t != null) - { - if(t instanceof IOException) - { - throw (IOException) pe.getCause(); - } - if(t instanceof SMBException) - { - throw pe.getCause(); - } - if(t instanceof DeviceContextException) - { - throw pe.getCause(); - } - throw t; - } - throw pe; - } + return tran.doInTransaction(callback, readOnly); } - else + catch(PropagatingException pe) { - // read/write only transaction - try + Throwable t = pe.getCause(); + if(t != null) { - return tran.doInTransaction(callback); - } - catch(PropagatingException pe) - { - Throwable t = pe.getCause(); - if(t != null) + if(t instanceof IOException) + { + throw (IOException) t; + } + if(t instanceof IOControlNotImplementedException) + { + throw (IOControlNotImplementedException) t; + } + if(t instanceof SMBException) + { + throw (SMBException)t; + } + if(t instanceof DeviceContextException) { - if(t instanceof IOException) - { - // Unwrap checked exceptions - throw pe.getCause(); - } - if(t instanceof SMBException) - { - throw pe.getCause(); - } - if(t instanceof DeviceContextException) - { - throw pe.getCause(); - } throw t; } - throw pe; - } + throw t; + } + throw pe; } } diff --git a/source/java/org/alfresco/filesys/repo/InFlightCorrectable.java b/source/java/org/alfresco/filesys/repo/InFlightCorrectable.java new file mode 100644 index 0000000000..999ecb0b6b --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/InFlightCorrectable.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2012 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.filesys.repo; + +/** + * + * @author mrogers + * + */ +public interface InFlightCorrectable +{ + public void setInFlightCorrector(InFlightCorrector correctable); +} diff --git a/source/java/org/alfresco/filesys/repo/InFlightCorrector.java b/source/java/org/alfresco/filesys/repo/InFlightCorrector.java new file mode 100644 index 0000000000..5643ce384d --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/InFlightCorrector.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2012 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.filesys.repo; + +import java.util.Date; + +import org.alfresco.jlan.server.filesys.FileInfo; +import org.alfresco.jlan.server.filesys.TreeConnection; +import org.alfresco.jlan.server.filesys.cache.FileState; +import org.alfresco.jlan.server.filesys.cache.FileStateCache; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * The in flight corrector corrects search results that have not yet been committed to the + * repository. + * + * It substitutes the "in flight" valuses from the state cache in place of the values committed to + * the repo + * + * @author mrogers + */ +public interface InFlightCorrector +{ + /** + * Correct thr results with in flight details. + * @param info + * @param folderPath + */ + public void correct(FileInfo info, String folderPath); +} diff --git a/source/java/org/alfresco/filesys/repo/InFlightCorrectorImpl.java b/source/java/org/alfresco/filesys/repo/InFlightCorrectorImpl.java new file mode 100644 index 0000000000..e309436b37 --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/InFlightCorrectorImpl.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2012 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.filesys.repo; + +import java.util.Date; + +import org.alfresco.jlan.server.filesys.FileInfo; +import org.alfresco.jlan.server.filesys.TreeConnection; +import org.alfresco.jlan.server.filesys.cache.FileState; +import org.alfresco.jlan.server.filesys.cache.FileStateCache; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * The in flight corrector corrects search results that have not yet been committed to the + * repository. + * + * It substitutes the "in flight" valuses from the state cache in place of the values committed to + * the repo + * + * @author mrogers + */ +public class InFlightCorrectorImpl implements InFlightCorrector +{ + TreeConnection tree; + + private static final Log logger = LogFactory.getLog(InFlightCorrectorImpl.class); + + public InFlightCorrectorImpl(TreeConnection tree) + { + this.tree = tree; + } + public void correct(FileInfo info, String folderPath) + { + ContentContext tctx = (ContentContext) tree.getContext(); + + String path = folderPath + info.getFileName(); + + if(tctx.hasStateCache()) + { + FileStateCache cache = tctx.getStateCache(); + FileState fstate = cache.findFileState( path, true); + + if(fstate != null) + { + logger.debug("correct " + path); + /* + * What about stale file state values here? + */ + if(fstate.hasFileSize()) + { + if(logger.isDebugEnabled()) + { + logger.debug("replace file size " + info.getSize() + " with " + fstate.getFileSize()); + } + info.setFileSize(fstate.getFileSize()); + } + if ( fstate.hasAccessDateTime()) + { + if(logger.isDebugEnabled()) + { + logger.debug("replace access date " + new Date(info.getAccessDateTime()) + " with " + new Date(fstate.getAccessDateTime())); + } + info.setAccessDateTime(fstate.getAccessDateTime()); + } + if ( fstate.hasChangeDateTime()) + { + if(logger.isDebugEnabled()) + { + logger.debug("replace change date " + new Date(info.getChangeDateTime()) + " with " + new Date(fstate.getChangeDateTime())); + } + info.setChangeDateTime(fstate.getChangeDateTime()); + } + if ( fstate.hasModifyDateTime()) + { + if(logger.isDebugEnabled()) + { + logger.debug("replace modified date " + new Date(info.getModifyDateTime()) + " with " + new Date(fstate.getModifyDateTime())); + } + info.setModifyDateTime(fstate.getModifyDateTime()); + } + if ( fstate.hasAllocationSize()) + { + if(logger.isDebugEnabled()) + { + logger.debug("replace allocation size" + info.getAllocationSize() + " with " + fstate.getAllocationSize()); + } + info.setAllocationSize(fstate.getAllocationSize()); + } + } + } + } + +} diff --git a/source/java/org/alfresco/filesys/repo/LegacyFileStateDriver.java b/source/java/org/alfresco/filesys/repo/LegacyFileStateDriver.java index 44c5831f54..c5464d4add 100644 --- a/source/java/org/alfresco/filesys/repo/LegacyFileStateDriver.java +++ b/source/java/org/alfresco/filesys/repo/LegacyFileStateDriver.java @@ -301,28 +301,25 @@ public class LegacyFileStateDriver implements ExtendedDiskInterface { FileStateCache cache = tctx.getStateCache(); FileState fstate = cache.findFileState( param.getFullName(), true); - - if(fstate.getOpenCount() ==0 ) + + if(fstate != null && param.getAccessToken() != null) { - logger.debug("OpenCount = 0, reset shared access to READ WRITE DELETE"); - fstate.setSharedAccess( SharingMode.READWRITE + SharingMode.DELETE); - + FileAccessToken token = param.getAccessToken(); + if(logger.isDebugEnabled() && token != null) + { + logger.debug("close file, release access token:" + token); + } + cache.releaseFileAccess(fstate, token); + } + + if(fstate.getOpenCount() == 0 ) + { + logger.debug("fstate OpenCount == 0, reset in-flight state"); fstate.setAllocationSize(-1); fstate.setFileSize(-1); fstate.updateChangeDateTime(0); fstate.updateModifyDateTime(0); } - - if(fstate != null && param.getAccessToken() != null) - { - - FileAccessToken token = param.getAccessToken(); - if(logger.isDebugEnabled() && token != null) - { - logger.debug("close file release access token:" + token); - } - cache.releaseFileAccess(fstate, token); - } } } catch(IOException ie) @@ -459,7 +456,17 @@ public class LegacyFileStateDriver implements ExtendedDiskInterface public SearchContext startSearch(SrvSession sess, TreeConnection tree, String searchPath, int attrib) throws FileNotFoundException { - return diskInterface.startSearch(sess, tree, searchPath, attrib); + InFlightCorrector t = new InFlightCorrectorImpl(tree); + + SearchContext ctx = diskInterface.startSearch(sess, tree, searchPath, attrib); + + if(ctx instanceof InFlightCorrectable) + { + InFlightCorrectable thingable = (InFlightCorrectable)ctx; + thingable.setInFlightCorrector(t); + } + + return ctx; } diff --git a/source/java/org/alfresco/filesys/repo/TempNetworkFile.java b/source/java/org/alfresco/filesys/repo/TempNetworkFile.java index 62d4192f21..500e57b89d 100644 --- a/source/java/org/alfresco/filesys/repo/TempNetworkFile.java +++ b/source/java/org/alfresco/filesys/repo/TempNetworkFile.java @@ -84,6 +84,7 @@ public class TempNetworkFile extends JavaNetworkFile implements NetworkFileState fileState.updateModifyDateTime(); fileState.updateAccessDateTime(); fileState.setFileSize(size); + fileState.setAllocationSize((size + 512L) & 0xFFFFFFFFFFFFFE00L); } } @@ -102,6 +103,7 @@ public class TempNetworkFile extends JavaNetworkFile implements NetworkFileState fileState.updateModifyDateTime(); fileState.updateAccessDateTime(); fileState.setFileSize(size); + fileState.setAllocationSize((size + 512L) & 0xFFFFFFFFFFFFFE00L); } } @@ -121,6 +123,7 @@ public class TempNetworkFile extends JavaNetworkFile implements NetworkFileState fileState.updateModifyDateTime(); fileState.updateAccessDateTime(); fileState.setFileSize(size); + fileState.setAllocationSize((size + 512L) & 0xFFFFFFFFFFFFFE00L); } } diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioCreateDeleteRenameShuffle.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioCreateDeleteRenameShuffle.java new file mode 100644 index 0000000000..ad004c279d --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioCreateDeleteRenameShuffle.java @@ -0,0 +1,114 @@ +/* + * 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 . + */ +package org.alfresco.filesys.repo.rules; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.alfresco.filesys.repo.rules.ScenarioInstance.Ranking; +import org.alfresco.filesys.repo.rules.operations.CreateFileOperation; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * A create, delete, rename, shuffle + * + * a) New file created. + * b) Existing file deleted + * c) New file moved into place. + */ +public class ScenarioCreateDeleteRenameShuffle implements Scenario +{ + private static Log logger = LogFactory.getLog(ScenarioCreateDeleteRenameShuffle.class); + + /** + * The regex pattern of a create that will trigger a new instance of + * the scenario. + */ + private Pattern pattern; + private String strPattern; + + + private long timeout = 30000; + + private Ranking ranking = Ranking.HIGH; + + @Override + public ScenarioInstance createInstance(final List currentInstances, Operation operation) + { + /** + * This scenario is triggered by a create of a file matching + * the pattern + */ + if(operation instanceof CreateFileOperation) + { + CreateFileOperation c = (CreateFileOperation)operation; + + Matcher m = pattern.matcher(c.getName()); + if(m.matches()) + { + if(logger.isDebugEnabled()) + { + logger.debug("New Scenario Create Delete Rename Shuffle Instance pattern:" + strPattern); + } + + ScenarioCreateDeleteRenameShuffleInstance instance = new ScenarioCreateDeleteRenameShuffleInstance() ; + instance.setTimeout(timeout); + instance.setRanking(ranking); + return instance; + } + } + + // No not interested. + return null; + + } + + public void setPattern(String pattern) + { + this.pattern = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE); + this.strPattern = pattern; + } + + public String getPattern() + { + return this.strPattern; + } + + public void setTimeout(long timeout) + { + this.timeout = timeout; + } + + public long getTimeout() + { + return timeout; + } + + public void setRanking(Ranking ranking) + { + this.ranking = ranking; + } + + public Ranking getRanking() + { + return ranking; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioCreateDeleteRenameShuffleInstance.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioCreateDeleteRenameShuffleInstance.java new file mode 100644 index 0000000000..179f70336f --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioCreateDeleteRenameShuffleInstance.java @@ -0,0 +1,295 @@ +/* + * 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 . + */ +package org.alfresco.filesys.repo.rules; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import org.alfresco.filesys.repo.rules.ScenarioLockedDeleteShuffleInstance.InternalState; +import org.alfresco.filesys.repo.rules.commands.CompoundCommand; +import org.alfresco.filesys.repo.rules.commands.CopyContentCommand; +import org.alfresco.filesys.repo.rules.commands.DeleteFileCommand; +import org.alfresco.filesys.repo.rules.commands.RenameFileCommand; +import org.alfresco.filesys.repo.rules.operations.CreateFileOperation; +import org.alfresco.filesys.repo.rules.operations.DeleteFileOperation; +import org.alfresco.filesys.repo.rules.operations.MoveFileOperation; +import org.alfresco.filesys.repo.rules.operations.RenameFileOperation; +import org.alfresco.jlan.server.filesys.FileName; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * This is an instance of a create, delete, rename shuffle" triggered by a create of a + * file matching a specified pattern. + *

+ * a) New file created. Typically with an obscure name. + * b) Existing file deleted + * c) New file moved into place. + * + *

+ * If this filter is active then this is what happens. + * a) New file created. New file created (X). + * b) Existing file deleted (Y to Z). File moved to temporary location. + * c) Rename - Scenario fires + * - File moved back from temporary location + * - Content updated/ + * - temporary file deleted + */ +public class ScenarioCreateDeleteRenameShuffleInstance implements ScenarioInstance +{ + private static Log logger = LogFactory.getLog(ScenarioCreateDeleteRenameShuffleInstance.class); + + enum InternalState + { + NONE, + ACTIVE + } + + InternalState internalState = InternalState.NONE; + + private Date startTime = new Date(); + + private String createName; + private Ranking ranking; + private boolean checkFilename=true; + + /** + * Timeout in ms. Default 30 seconds. + */ + private long timeout = 60000; + + private boolean isComplete; + + /** + * Keep track of deletes that we substitute with a rename + * could be more than one if scenarios overlap + * + * From, TempFileName + */ + private Map deletes = new HashMap(); + + /** + * Evaluate the next operation + * @param operation + */ + public Command evaluate(Operation operation) + { + + /** + * Anti-pattern : timeout + */ + Date now = new Date(); + if(now.getTime() > startTime.getTime() + getTimeout()) + { + if(logger.isDebugEnabled()) + { + logger.debug("Instance timed out createName:" + createName); + isComplete = true; + return null; + } + } + + /** + * Anti-pattern for all states - delete the file we are + * shuffling + */ + if(createName != null) + { + if(operation instanceof DeleteFileOperation) + { + DeleteFileOperation d = (DeleteFileOperation)operation; + if(d.getName().equals(createName)) + { + if(logger.isDebugEnabled()) + { + logger.debug("Anti-pattern : Shuffle file deleted createName:" + createName); + } + isComplete = true; + return null; + } + } + } + + switch (internalState) + { + + case NONE: + // Looking for a create transition + if(operation instanceof CreateFileOperation) + { + CreateFileOperation c = (CreateFileOperation)operation; + this.createName = c.getName(); + if(logger.isDebugEnabled()) + { + logger.debug("entering ACTIVE state: " + createName); + } + internalState = InternalState.ACTIVE; + return null; + } + else + { + // anything else bomb out + if(logger.isDebugEnabled()) + { + logger.debug("State error, expected a CREATE"); + } + isComplete = true; + } + break; + + case ACTIVE: + + /** + * Looking for deletes and renames + */ + + /** + * Looking for target file being deleted + * + * Need to intervene and replace delete with a rename to temp file. + */ + if(operation instanceof DeleteFileOperation) + { + DeleteFileOperation d = (DeleteFileOperation)operation; + + String deleteName = d.getName(); + + /** + * For Mac 2011 powerpoint files - add an extra check based on the filename + * e.g FileA1 - delete FileA. + */ + if(checkFilename) + { + int i = deleteName.lastIndexOf('.'); + + if(i > 0 && deleteName.substring(0, i).equalsIgnoreCase(createName.substring(0,i))) + { + logger.debug("check filenames - does match"); + } + else + { + if(logger.isDebugEnabled()) + { + logger.debug("check filename patterns do not match - Ignore" + createName + deleteName); + } + return null; + } + } + + if(logger.isDebugEnabled()) + { + logger.debug("got a delete : replace with rename createName:" + createName + "deleteName:" + deleteName); + } + + String tempName = ".shuffle" + d.getName(); + + deletes.put(d.getName(), tempName); + + String[] paths = FileName.splitPath(d.getPath()); + String currentFolder = paths[0]; + + RenameFileCommand r1 = new RenameFileCommand(d.getName(), tempName, d.getRootNodeRef(), d.getPath(), currentFolder + "\\" + tempName); + + return r1; + } + + /** + * + */ + if(operation instanceof RenameFileOperation) + { + RenameFileOperation m = (RenameFileOperation)operation; + + String targetFile = m.getTo(); + + if(deletes.containsKey(targetFile)) + { + String tempName = deletes.get(targetFile); + + String[] paths = FileName.splitPath(m.getToPath()); + String currentFolder = paths[0]; + + /** + * This is where the scenario fires. + * a) Rename the temp file back to the targetFile + * b) Copy content from moved file + * c) Delete rather than move file + */ + if(logger.isDebugEnabled()) + { + logger.debug("scenario fires:" + createName); + } + ArrayList commands = new ArrayList(); + + RenameFileCommand r1 = new RenameFileCommand(tempName, targetFile, m.getRootNodeRef(), currentFolder + "\\" + tempName, m.getToPath()); + + CopyContentCommand copyContent = new CopyContentCommand(m.getFrom(), targetFile, m.getRootNodeRef(), m.getFromPath(), m.getToPath()); + + DeleteFileCommand d1 = new DeleteFileCommand(m.getFrom(), m.getRootNodeRef(), m.getFromPath()); + + commands.add(r1); + commands.add(copyContent); + commands.add(d1); + + logger.debug("Scenario complete"); + isComplete = true; + + return new CompoundCommand(commands); + } + } + + } + + return null; + } + + @Override + public boolean isComplete() + { + return isComplete; + } + + @Override + public Ranking getRanking() + { + return ranking; + } + + public void setRanking(Ranking ranking) + { + this.ranking = ranking; + } + + public String toString() + { + return "ScenarioShuffleInstance: createName:" + createName; + } + + public void setTimeout(long timeout) + { + this.timeout = timeout; + } + + public long getTimeout() + { + return timeout; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioCreateShuffleInstance.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioCreateShuffleInstance.java index b782a74c4a..1428a2ec96 100644 --- a/source/java/org/alfresco/filesys/repo/rules/ScenarioCreateShuffleInstance.java +++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioCreateShuffleInstance.java @@ -72,7 +72,7 @@ public class ScenarioCreateShuffleInstance implements ScenarioInstance /** * Timeout in ms. Default 30 seconds. */ - private long timeout = 30000; + private long timeout = 60000; private boolean isComplete; @@ -88,24 +88,6 @@ public class ScenarioCreateShuffleInstance implements ScenarioInstance public Command evaluate(Operation operation) { -// /** -// * Anti-pattern for all states - delete the file we are -// * shuffling -// */ -// if(createName != null) -// { -// if(operation instanceof DeleteFileOperation) -// { -// DeleteFileOperation d = (DeleteFileOperation)operation; -// if(d.getName().equals(createName)) -// { -// logger.debug("Anti-pattern : Shuffle file deleted"); -// isComplete = true; -// return null; -// } -// } -// } - /** * Anti-pattern : timeout */ @@ -114,7 +96,30 @@ public class ScenarioCreateShuffleInstance implements ScenarioInstance { if(logger.isDebugEnabled()) { - logger.debug("Instance timed out"); + logger.debug("Instance timed out createName:" + createName); + isComplete = true; + return null; + } + } + + /** + * Anti-pattern for all states - delete the file we are + * shuffling + */ + if(createName != null) + { + if(operation instanceof DeleteFileOperation) + { + DeleteFileOperation d = (DeleteFileOperation)operation; + if(d.getName().equals(createName)) + { + if(logger.isDebugEnabled()) + { + logger.debug("Anti-pattern : Shuffle file deleted createName:" + createName); + } + isComplete = true; + return null; + } } } @@ -229,11 +234,11 @@ public class ScenarioCreateShuffleInstance implements ScenarioInstance DeleteFileOperation d = (DeleteFileOperation)operation; if(d.getName().equals(move2)) { - logger.debug("Scenario complete"); + if(logger.isDebugEnabled()) + { + logger.debug("Scenario complete createName:" + createName); + } isComplete = true; -// ArrayList commands = new ArrayList(); -// // missing stuff here -// return new CompoundCommand(commands); } } @@ -262,7 +267,7 @@ public class ScenarioCreateShuffleInstance implements ScenarioInstance public String toString() { - return "ScenarioShuffleInstance:" + createName; + return "ScenarioShuffleInstance: createName:" + createName; } public void setTimeout(long timeout) diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioDoubleRenameShuffleInstance.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioDoubleRenameShuffleInstance.java index f79f3a341e..31fe2f3213 100644 --- a/source/java/org/alfresco/filesys/repo/rules/ScenarioDoubleRenameShuffleInstance.java +++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioDoubleRenameShuffleInstance.java @@ -95,6 +95,7 @@ public class ScenarioDoubleRenameShuffleInstance implements ScenarioInstance if(logger.isDebugEnabled()) { logger.debug("Instance timed out"); + } } diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioLockedDeleteShuffleInstance.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioLockedDeleteShuffleInstance.java index d83ad8d0c3..8d0636a801 100644 --- a/source/java/org/alfresco/filesys/repo/rules/ScenarioLockedDeleteShuffleInstance.java +++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioLockedDeleteShuffleInstance.java @@ -79,11 +79,10 @@ public class ScenarioLockedDeleteShuffleInstance implements ScenarioInstance private Ranking ranking; - /** * Timeout in ms. Default 30 seconds. */ - private long timeout = 30000; + private long timeout = 60000; private boolean isComplete; @@ -127,7 +126,9 @@ public class ScenarioLockedDeleteShuffleInstance implements ScenarioInstance { if(logger.isDebugEnabled()) { - logger.debug("Instance timed out"); + logger.debug("Instance timed out lockName:" + lockName); + isComplete = true; + return null; } } @@ -212,7 +213,8 @@ public class ScenarioLockedDeleteShuffleInstance implements ScenarioInstance * a) Rename the temp file back to the targetFile * b) Copy content from moved file * c) Delete rather than move file - */ + */ + logger.debug("scenario fires"); ArrayList commands = new ArrayList(); RenameFileCommand r1 = new RenameFileCommand(tempName, targetFile, m.getRootNodeRef(), currentFolder + "\\" + tempName, m.getToPath()); @@ -228,17 +230,8 @@ public class ScenarioLockedDeleteShuffleInstance implements ScenarioInstance logger.debug("Scenario complete"); isComplete = true; - return new CompoundCommand(commands); - + return new CompoundCommand(commands); } - - //TODO - Need to consider error cases and "overlap" - -// if(logger.isDebugEnabled()) -// { -// logger.debug("entering MOVED state: " + lockName); -// } -// internalState = InternalState.MOVED; } diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioOpenFile.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioOpenFile.java index ce3e0bd571..1e43724699 100644 --- a/source/java/org/alfresco/filesys/repo/rules/ScenarioOpenFile.java +++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioOpenFile.java @@ -169,7 +169,7 @@ public class ScenarioOpenFile implements Scenario ScenarioOpenFileInstance i = (ScenarioOpenFileInstance)instance; if(i.getName() != null && name != null) { - if(i.getName().equalsIgnoreCase(name)); + if(i.getName().equalsIgnoreCase(name)) { return true; } diff --git a/source/java/org/alfresco/repo/avm/AVMLockingAwareService.java b/source/java/org/alfresco/repo/avm/AVMLockingAwareService.java index a9bb46e692..13e1b2e0d6 100644 --- a/source/java/org/alfresco/repo/avm/AVMLockingAwareService.java +++ b/source/java/org/alfresco/repo/avm/AVMLockingAwareService.java @@ -31,6 +31,7 @@ import org.alfresco.repo.avm.util.AVMUtil; import org.alfresco.repo.domain.PropertyValue; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.permissions.AccessDeniedException; +import org.alfresco.service.cmr.avm.AVMExistsException; import org.alfresco.service.cmr.avm.AVMNodeDescriptor; import org.alfresco.service.cmr.avm.AVMService; import org.alfresco.service.cmr.avm.AVMStoreDescriptor; @@ -64,6 +65,8 @@ public class AVMLockingAwareService implements AVMService, ApplicationContextAwa public static final String STORE_WORKFLOW = "workflow"; + public static final String STORE_PREVIEW = "preview"; + private AVMService fService; private AVMLockingService fLockingService; @@ -668,14 +671,27 @@ public class AVMLockingAwareService implements AVMService, ApplicationContextAwa { String userName = AuthenticationUtil.getFullyAuthenticatedUser(); LockState lockState = fLockingService.getLockState(webProject, storePath[1], userName); - // Managers can edit any file in any sandbox, look into ALF-11440 String wpStoreId = WCMUtil.getWebProjectStoreId(webProject); - if (lockState == AVMLockingService.LockState.LOCK_NOT_OWNER && wpService.isContentManager(wpStoreId, userName)) - lockState = AVMLockingService.LockState.LOCK_OWNER; + + // ALF-11440 PM 18-Dec-2011: + // 1. Managers may edit any unlocked file - it becomes locked. + // 2. Managers may edit any locked file in any sandbox in which it is locked + // but not in sandboxes where it is unlocked. + // ALF-8787 and ALF-12766 are consistent with 2. + // A Manager should only be able to create a file in a sandbox + // if it is NOT locked somewhere else. switch (lockState) { case LOCK_NOT_OWNER: String lockOwner = fLockingService.getLockOwner(webProject, storePath[1]); + if ((wpService.isContentManager(wpStoreId, userName)) && + (avmStore.equals(wpStoreId + STORE_SEPARATOR + lockOwner) || + avmStore.equals(wpStoreId + STORE_SEPARATOR + lockOwner + + STORE_SEPARATOR + STORE_PREVIEW))) + { + // Handle as if LOCK_OWNER + break; + } throw new AVMLockingException("avmlockservice.locked", path, lockOwner); case NO_LOCK: Map lockAttributes = Collections.singletonMap(WCMUtil.LOCK_KEY_STORE_NAME, avmStore); diff --git a/source/java/org/alfresco/repo/avm/AVMSyncServiceImpl.java b/source/java/org/alfresco/repo/avm/AVMSyncServiceImpl.java index 1e9c0e7452..375c3ae2e9 100644 --- a/source/java/org/alfresco/repo/avm/AVMSyncServiceImpl.java +++ b/source/java/org/alfresco/repo/avm/AVMSyncServiceImpl.java @@ -232,7 +232,7 @@ public class AVMSyncServiceImpl implements AVMSyncService dirDiffCode)); // Also add all child items if necessary and any exists - if (expandDirs) + if (expandDirs && srcDesc.isDirectory()) { addNewChildrenIfAny(srcVersion, srcDesc, dstVersion, AVMNodeConverter.ExtendAVMPath(dstPath, dstDesc.getName()), result); } @@ -283,7 +283,7 @@ public class AVMSyncServiceImpl implements AVMSyncService AVMDifference.NEWER)); // Also add all child items if necessary and any exists - if (expandDirs) + if (expandDirs && srcChild.isDirectory()) { addNewChildrenIfAny(srcVersion, srcChild, dstVersion, dstChildPath, result); } diff --git a/source/java/org/alfresco/repo/content/AbstractContentReader.java b/source/java/org/alfresco/repo/content/AbstractContentReader.java index 5e4ae7432e..5e7e2688c0 100644 --- a/source/java/org/alfresco/repo/content/AbstractContentReader.java +++ b/source/java/org/alfresco/repo/content/AbstractContentReader.java @@ -69,7 +69,7 @@ import sun.nio.ch.ChannelInputStream; public abstract class AbstractContentReader extends AbstractContentAccessor implements ContentReader { private static final Log logger = LogFactory.getLog(AbstractContentReader.class); - private static final Timer timer = new Timer(); + private static final Timer timer = new Timer(true); private List listeners; private ReadableByteChannel channel; diff --git a/source/java/org/alfresco/repo/content/transform/PdfToImageContentTransformer.java b/source/java/org/alfresco/repo/content/transform/PdfToImageContentTransformer.java index 6acb28be1e..1eedb7656f 100644 --- a/source/java/org/alfresco/repo/content/transform/PdfToImageContentTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/PdfToImageContentTransformer.java @@ -93,6 +93,11 @@ public class PdfToImageContentTransformer extends AbstractContentTransformer2 } PDFPage page = pdffile.getPage(0, true); + if (page == null) + { + throw new AlfrescoRuntimeException("Unable to create image from pdf file."+ + "A PDFRender error took place which should have been sent to stdout."); + } //get the width and height for the doc at the default zoom int width=(int)page.getBBox().getWidth(); diff --git a/source/java/org/alfresco/repo/content/transform/magick/ImageMagickContentTransformerWorker.java b/source/java/org/alfresco/repo/content/transform/magick/ImageMagickContentTransformerWorker.java index 9f3ffda96f..f277ed5b72 100644 --- a/source/java/org/alfresco/repo/content/transform/magick/ImageMagickContentTransformerWorker.java +++ b/source/java/org/alfresco/repo/content/transform/magick/ImageMagickContentTransformerWorker.java @@ -154,6 +154,10 @@ public class ImageMagickContentTransformerWorker extends AbstractImageMagickCont ImageCropOptions cropOptions = imageOptions.getCropOptions(); ImageResizeOptions resizeOptions = imageOptions.getResizeOptions(); String commandOptions = imageOptions.getCommandOptions(); + if (commandOptions == null) + { + commandOptions = ""; + } if (imageOptions.isAutoOrient()) { commandOptions = commandOptions + " -auto-orient"; diff --git a/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java b/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java index 7ac064e6e4..cc758ce17a 100644 --- a/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java @@ -57,10 +57,10 @@ import org.alfresco.repo.domain.usage.UsageDAO; import org.alfresco.repo.policy.BehaviourFilter; import org.alfresco.repo.security.permissions.AccessControlListProperties; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; -import org.alfresco.repo.transaction.TransactionAwareSingleton; -import org.alfresco.repo.transaction.TransactionListenerAdapter; import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.repo.transaction.TransactionAwareSingleton; +import org.alfresco.repo.transaction.TransactionListenerAdapter; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.dictionary.InvalidTypeException; @@ -74,20 +74,20 @@ import org.alfresco.service.cmr.repository.DuplicateChildNodeNameException; import org.alfresco.service.cmr.repository.InvalidNodeRefException; import org.alfresco.service.cmr.repository.InvalidStoreRefException; import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeRef.Status; import org.alfresco.service.cmr.repository.Path; import org.alfresco.service.cmr.repository.StoreRef; -import org.alfresco.service.cmr.repository.NodeRef.Status; import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; import org.alfresco.service.namespace.QName; import org.alfresco.service.transaction.ReadOnlyServerException; import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.EqualsHelper; +import org.alfresco.util.EqualsHelper.MapValueComparison; import org.alfresco.util.GUID; import org.alfresco.util.Pair; import org.alfresco.util.PropertyCheck; import org.alfresco.util.ReadWriteLockExecuter; import org.alfresco.util.ValueProtectingMap; -import org.alfresco.util.EqualsHelper.MapValueComparison; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.dao.ConcurrencyFailureException; @@ -957,12 +957,8 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO Long nodeId = dbNode.getId(); if (dbNode.getDeleted()) { - // The node is actually deleted as the cache said. Could still be a race condition, so let's allow the - // transaction to be retried by attaching a cause to our InvalidNodeRefException - InvalidNodeRefException e = new InvalidNodeRefException(nodeRef); - e.initCause(new ConcurrencyFailureException("Attempt to follow reference " + nodeRef - + " to deleted node " + nodeId)); - throw e; + // The node is actually deleted as the cache said. + throw new InvalidNodeRefException(nodeRef); } else { @@ -4248,7 +4244,7 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO { return selectTxnsUnused(minTxnId, maxCommitTime, count); } - + public void purgeTxn(Long txnId) { deleteTransaction(txnId); @@ -4268,6 +4264,24 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO return (time == null ? LONG_ZERO : time); } + public Long getMinTxnId() + { + Long id = selectMinTxnId(); + return (id == null ? LONG_ZERO : id); + } + + public Long getMinUnusedTxnCommitTime() + { + Long id = selectMinUnusedTxnCommitTime(); + return (id == null ? LONG_ZERO : id); + } + + public Long getMaxTxnId() + { + Long id = selectMaxTxnId(); + return (id == null ? LONG_ZERO : id); + } + /* * Abstract methods for underlying CRUD */ @@ -4438,4 +4452,7 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO protected abstract List selectTxnsUnused(Long minTxnId, Long maxCommitTime, Integer count); protected abstract Long selectMinTxnCommitTime(); protected abstract Long selectMaxTxnCommitTime(); + protected abstract Long selectMinTxnId(); + protected abstract Long selectMaxTxnId(); + protected abstract Long selectMinUnusedTxnCommitTime(); } diff --git a/source/java/org/alfresco/repo/domain/node/NodeDAO.java b/source/java/org/alfresco/repo/domain/node/NodeDAO.java index 003f674401..bd0043725d 100644 --- a/source/java/org/alfresco/repo/domain/node/NodeDAO.java +++ b/source/java/org/alfresco/repo/domain/node/NodeDAO.java @@ -710,6 +710,16 @@ public interface NodeDAO extends NodeBulkLoader public List getTxnsUnused(Long minTxnId, long maxCommitTime, int count); + /** + * Remove unused transactions from commit time 'fromCommitTime' to commit time 'toCommitTime' + * + * @param fromCommitTime delete unused transactions from commit time + * @param toCommitTime delete unused transactions to commit time + * + * @return + */ + public int deleteTxnsUnused(long fromCommitTime, long toCommitTime); + public void purgeTxn(Long txnId); /** @@ -722,6 +732,22 @@ public interface NodeDAO extends NodeBulkLoader */ public Long getMaxTxnCommitTime(); + /** + * @return Returns the minimum id or 0 if there are no transactions + */ + public Long getMinTxnId(); + + /** + * + * @return the commit time of the oldest unused transaction + */ + public Long getMinUnusedTxnCommitTime(); + + /** + * @return Returns the maximum id or 0 if there are no transactions + */ + public Long getMaxTxnId(); + /** * Select children by property values */ diff --git a/source/java/org/alfresco/repo/domain/node/ibatis/NodeDAOImpl.java b/source/java/org/alfresco/repo/domain/node/ibatis/NodeDAOImpl.java index b92e3223e8..f381c2bcc7 100644 --- a/source/java/org/alfresco/repo/domain/node/ibatis/NodeDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/node/ibatis/NodeDAOImpl.java @@ -142,8 +142,12 @@ public class NodeDAOImpl extends AbstractNodeDAOImpl private static final String SELECT_TXN_COUNT = "alfresco.node.select_TxnCount"; private static final String SELECT_TXN_NODE_COUNT = "alfresco.node.select_TxnNodeCount"; private static final String SELECT_TXNS_UNUSED = "alfresco.node.select_TxnsUnused"; + private static final String DELETE_TXNS_UNUSED = "alfresco.node.delete_Txns_Unused"; private static final String SELECT_TXN_MIN_COMMIT_TIME = "alfresco.node.select_TxnMinCommitTime"; private static final String SELECT_TXN_MAX_COMMIT_TIME = "alfresco.node.select_TxnMaxCommitTime"; + private static final String SELECT_TXN_MIN_ID = "alfresco.node.select_TxnMinId"; + private static final String SELECT_TXN_MAX_ID = "alfresco.node.select_TxnMaxId"; + private static final String SELECT_TXN_UNUSED_MIN_COMMIT_TIME = "alfresco.node.select_TxnMinUnusedCommitTime"; private QNameDAO qnameDAO; private DictionaryService dictionaryService; @@ -1605,6 +1609,16 @@ public class NodeDAOImpl extends AbstractNodeDAOImpl } } + @Override + public int deleteTxnsUnused(long fromCommitTime, long toCommitTime) + { + TransactionQueryEntity txnQuery = new TransactionQueryEntity(); + txnQuery.setMinCommitTime(fromCommitTime); + txnQuery.setMaxCommitTime(toCommitTime); + int numDeleted = template.delete(DELETE_TXNS_UNUSED, txnQuery); + return numDeleted; + } + @Override protected Long selectMinTxnCommitTime() { @@ -1616,6 +1630,24 @@ public class NodeDAOImpl extends AbstractNodeDAOImpl { return (Long) template.selectOne(SELECT_TXN_MAX_COMMIT_TIME); } + + @Override + protected Long selectMinTxnId() + { + return (Long) template.selectOne(SELECT_TXN_MIN_ID); + } + + @Override + protected Long selectMinUnusedTxnCommitTime() + { + return (Long) template.selectOne(SELECT_TXN_UNUSED_MIN_COMMIT_TIME); + } + + @Override + protected Long selectMaxTxnId() + { + return (Long) template.selectOne(SELECT_TXN_MAX_ID); + } @Override public List selectProperties(Collection propertyDefs) diff --git a/source/java/org/alfresco/repo/domain/permissions/AbstractAclCrudDAOImpl.java b/source/java/org/alfresco/repo/domain/permissions/AbstractAclCrudDAOImpl.java index 35822aac7d..1ea9317d2b 100644 --- a/source/java/org/alfresco/repo/domain/permissions/AbstractAclCrudDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/permissions/AbstractAclCrudDAOImpl.java @@ -1021,4 +1021,38 @@ public abstract class AbstractAclCrudDAOImpl implements AclCrudDAO protected abstract long createAuthorityAliasEntity(AuthorityAliasEntity entity); protected abstract int deleteAuthorityAliasEntity(long id); + + + /* (non-Javadoc) + * @see org.alfresco.repo.domain.permissions.AclCrudDAO#getMaxChangeSetCommitTime() + */ + @Override + public Long getMaxChangeSetCommitTime() + { + Long time = selectMaxChangeSetCommitTime(); + return (time == null ? 0L : time); + } + + + /** + * @return + */ + protected abstract Long selectMaxChangeSetCommitTime(); + + + /* (non-Javadoc) + * @see org.alfresco.repo.domain.permissions.AclCrudDAO#getMaxChangeSetIdByCommitTime(long) + */ + @Override + public Long getMaxChangeSetIdByCommitTime(long maxCommitTime) + { + Long id = selectMaxChangeSetIdBeforeCommitTime(maxCommitTime); + return (id == null ? 0L : id); + } + + /** + * @param maxCommitTime + * @return + */ + protected abstract Long selectMaxChangeSetIdBeforeCommitTime(long maxCommitTime); } diff --git a/source/java/org/alfresco/repo/domain/permissions/AclCrudDAO.java b/source/java/org/alfresco/repo/domain/permissions/AclCrudDAO.java index d57018ce5b..b68a6ab7bc 100644 --- a/source/java/org/alfresco/repo/domain/permissions/AclCrudDAO.java +++ b/source/java/org/alfresco/repo/domain/permissions/AclCrudDAO.java @@ -112,6 +112,15 @@ public interface AclCrudDAO public Authority getOrCreateAuthority(String authorityName); public void renameAuthority(String authorityNameBefore, String authorityAfter); public void deleteAuthority(long authorityEntityId); + /** + * @return + */ + public Long getMaxChangeSetCommitTime(); + /** + * @param maxCommitTime + * @return + */ + public Long getMaxChangeSetIdByCommitTime(long maxCommitTime); // AceContext (NOTE: currently unused - intended for possible future enhancement) // AuthorityAlias (NOTE: currently unused - intended for possible future enhancement) diff --git a/source/java/org/alfresco/repo/domain/permissions/AclDAO.java b/source/java/org/alfresco/repo/domain/permissions/AclDAO.java index 52ff222c69..b455c7eafc 100644 --- a/source/java/org/alfresco/repo/domain/permissions/AclDAO.java +++ b/source/java/org/alfresco/repo/domain/permissions/AclDAO.java @@ -160,4 +160,15 @@ public interface AclDAO * @param aclId */ public void fixSharedAcl(Long shared, Long defining); + + /** + * @return + */ + public Long getMaxChangeSetCommitTime(); + + /** + * @param maxCommitTime + * @return + */ + public Long getMaxChangeSetIdByCommitTime(long maxCommitTime); } diff --git a/source/java/org/alfresco/repo/domain/permissions/AclDAOImpl.java b/source/java/org/alfresco/repo/domain/permissions/AclDAOImpl.java index 4d7d9553e3..13b2fecd2e 100644 --- a/source/java/org/alfresco/repo/domain/permissions/AclDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/permissions/AclDAOImpl.java @@ -72,6 +72,8 @@ public class AclDAOImpl implements AclDAO private TenantService tenantService; private SimpleCache aclCache; private SimpleCache> readersCache; + + private SimpleCache> readersDeniedCache; private enum WriteMode { @@ -142,6 +144,14 @@ public class AclDAOImpl implements AclDAO { this.readersCache = readersCache; } + + /** + * @param readersDeniedCache the readersDeniedCache to set + */ + public void setReadersDeniedCache(SimpleCache> readersDeniedCache) + { + this.readersDeniedCache = readersDeniedCache; + } /** * {@inheritDoc} @@ -434,6 +444,7 @@ public class AclDAOImpl implements AclDAO { aclCache.remove(id); readersCache.remove(id); + readersDeniedCache.remove(id); return new AclChangeImpl(id, id, acl.getAclType(), acl.getAclType()); } @@ -484,6 +495,7 @@ public class AclDAOImpl implements AclDAO aclCrudDAO.updateAcl(acl); aclCache.remove(id); readersCache.remove(id); + readersDeniedCache.remove(id); return new AclChangeImpl(id, id, acl.getAclType(), acl.getAclType()); } else if ((acl.getAclChangeSetId() == getCurrentChangeSetId()) && (!requiresVersion) && (!acl.getRequiresVersion())) @@ -523,6 +535,7 @@ public class AclDAOImpl implements AclDAO aclCrudDAO.updateAcl(acl); aclCache.remove(id); readersCache.remove(id); + readersDeniedCache.remove(id); return new AclChangeImpl(id, id, acl.getAclType(), acl.getAclType()); } else @@ -612,6 +625,7 @@ public class AclDAOImpl implements AclDAO aclCrudDAO.updateAcl(acl); aclCache.remove(id); readersCache.remove(id); + readersDeniedCache.remove(id); return new AclChangeImpl(id, created, acl.getAclType(), newAcl.getAclType()); } } @@ -803,6 +817,7 @@ public class AclDAOImpl implements AclDAO { aclCache.remove(aclId); readersCache.remove(aclId); + readersDeniedCache.remove(aclId); Acl list = aclCrudDAO.getAcl(aclId); acls.add(new AclChangeImpl(aclId, aclId, list.getAclType(), list.getAclType())); @@ -860,6 +875,7 @@ public class AclDAOImpl implements AclDAO aclCache.remove(aclId); readersCache.remove(aclId); + readersDeniedCache.remove(aclId); } if (dbAcl.getAclType() == ACLType.SHARED) { @@ -878,6 +894,7 @@ public class AclDAOImpl implements AclDAO aclCache.remove(aclId); readersCache.remove(aclId); + readersDeniedCache.remove(aclId); } } else @@ -992,6 +1009,7 @@ public class AclDAOImpl implements AclDAO // remove the deleted acl from the cache aclCache.remove(id); readersCache.remove(id); + readersDeniedCache.remove(id); acls.add(new AclChangeImpl(id, null, acl.getAclType(), null)); return acls; } @@ -1337,6 +1355,7 @@ public class AclDAOImpl implements AclDAO aclCrudDAO.updateAcl(acl); aclCache.remove(id); readersCache.remove(id); + readersDeniedCache.remove(id); changes.add(new AclChangeImpl(id, id, acl.getAclType(), acl.getAclType())); return changes; case SHARED: @@ -1387,6 +1406,7 @@ public class AclDAOImpl implements AclDAO aclCrudDAO.updateAcl(acl); aclCache.remove(id); readersCache.remove(id); + readersDeniedCache.remove(id); changes.add(new AclChangeImpl(id, id, acl.getAclType(), acl.getAclType())); return changes; case SHARED: @@ -1422,6 +1442,7 @@ public class AclDAOImpl implements AclDAO aclCrudDAO.updateAcl(aclToCopy); aclCache.remove(toCopy); readersCache.remove(toCopy); + readersDeniedCache.remove(toCopy); inheritedId = getInheritedAccessControlList(toCopy); if ((inheritedId != null) && (!inheritedId.equals(toCopy))) { @@ -1431,6 +1452,7 @@ public class AclDAOImpl implements AclDAO aclCrudDAO.updateAcl(inheritedAcl); aclCache.remove(inheritedId); readersCache.remove(inheritedId); + readersDeniedCache.remove(inheritedId); } return toCopy; case REDIRECT: @@ -1908,4 +1930,22 @@ public class AclDAOImpl implements AclDAO List changes = new ArrayList(); getWritable(shared, defining, null, null, defining, true, changes, WriteMode.CHANGE_INHERITED); } + + /* (non-Javadoc) + * @see org.alfresco.repo.domain.permissions.AclDAO#getMaxChangeSetCommitTime() + */ + @Override + public Long getMaxChangeSetCommitTime() + { + return aclCrudDAO.getMaxChangeSetCommitTime(); + } + + /* (non-Javadoc) + * @see org.alfresco.repo.domain.permissions.AclDAO#getMaxChangeSetIdByCommitTime(long) + */ + @Override + public Long getMaxChangeSetIdByCommitTime(long maxCommitTime) + { + return aclCrudDAO.getMaxChangeSetIdByCommitTime(maxCommitTime); + } } diff --git a/source/java/org/alfresco/repo/domain/permissions/ibatis/AclCrudDAOImpl.java b/source/java/org/alfresco/repo/domain/permissions/ibatis/AclCrudDAOImpl.java index f8760f1ab0..9ee24b737a 100644 --- a/source/java/org/alfresco/repo/domain/permissions/ibatis/AclCrudDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/permissions/ibatis/AclCrudDAOImpl.java @@ -35,6 +35,7 @@ import org.alfresco.repo.domain.permissions.PermissionEntity; import org.alfresco.repo.security.permissions.ACEType; import org.apache.ibatis.session.RowBounds; import org.mybatis.spring.SqlSessionTemplate; +import org.springframework.util.Assert; /** * iBatis-specific implementation of the ACL Crud DAO. @@ -91,6 +92,9 @@ public class AclCrudDAOImpl extends AbstractAclCrudDAOImpl private static final String INSERT_AUTHORITY_ALIAS = "alfresco.permissions.insert.insert_AuthorityAlias"; private static final String DELETE_AUTHORITY_ALIAS = "alfresco.permissions.delete_AuthorityAlias"; + private static final String SELECT_CHANGE_SET_LAST = "alfresco.permissions.select_ChangeSetLast"; + private static final String SELECT_CHANGE_SET_MAX_COMMIT_TIME = "alfresco.permissions.select_ChangeSetMaxCommitTime";; + private SqlSessionTemplate template; @@ -472,4 +476,40 @@ public class AclCrudDAOImpl extends AbstractAclCrudDAOImpl return template.delete(DELETE_AUTHORITY_ALIAS, params); } + + + /* (non-Javadoc) + * @see org.alfresco.repo.domain.permissions.AbstractAclCrudDAOImpl#selectMaxChangeSetCommitTime() + */ + @Override + protected Long selectMaxChangeSetCommitTime() + { + return (Long) template.selectOne(SELECT_CHANGE_SET_MAX_COMMIT_TIME); + } + + + /* (non-Javadoc) + * @see org.alfresco.repo.domain.permissions.AbstractAclCrudDAOImpl#selectMaxChangeSetIdBeforeCommitTime(long) + */ + @Override + protected Long selectMaxChangeSetIdBeforeCommitTime(long maxCommitTime) + { + Assert.notNull(maxCommitTime, "maxCommitTime"); + + Map params = new HashMap(1); + params.put("commit_time_ms", maxCommitTime); + + List sets = (List) template.selectList(SELECT_CHANGE_SET_LAST, params, new RowBounds(0, 1)); + if (sets.size() > 0) + { + return sets.get(0); + } + else + { + return null; + } + } + + + } diff --git a/source/java/org/alfresco/repo/invitation/InvitationServiceImpl.java b/source/java/org/alfresco/repo/invitation/InvitationServiceImpl.java index 2c346cef8d..4229a6c50a 100644 --- a/source/java/org/alfresco/repo/invitation/InvitationServiceImpl.java +++ b/source/java/org/alfresco/repo/invitation/InvitationServiceImpl.java @@ -32,13 +32,16 @@ import java.util.Set; import org.alfresco.model.ContentModel; import org.alfresco.repo.admin.SysAdminParams; import org.alfresco.repo.node.NodeServicePolicies; +import org.alfresco.repo.node.NodeServicePolicies.BeforeDeleteNodePolicy; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationException; import org.alfresco.repo.security.authentication.PasswordGenerator; import org.alfresco.repo.security.authentication.UserNameGenerator; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.site.SiteModel; +import org.alfresco.repo.transaction.TransactionalResourceHelper; import org.alfresco.repo.workflow.CancelWorkflowActionExecuter; import org.alfresco.repo.workflow.WorkflowModel; import org.alfresco.repo.workflow.activiti.ActivitiConstants; @@ -151,6 +154,7 @@ public class InvitationServiceImpl implements InvitationService, NodeServicePoli // this.policyComponent.bindClassBehaviour(QName.createQName(NamespaceService.ALFRESCO_URI, "beforeDeleteNode"), SiteModel.TYPE_SITE, new JavaBehaviour(this, "beforeDeleteNode")); + this.policyComponent.bindClassBehaviour(BeforeDeleteNodePolicy.QNAME, ContentModel.TYPE_PERSON, new JavaBehaviour(this, "beforeDeleteNode")); } /** @@ -362,23 +366,27 @@ public class InvitationServiceImpl implements InvitationService, NodeServicePoli private void endInvitation(WorkflowTask startTask, String transition, Map properties, QName... taskTypes ) { - List tasks = workflowService.getTasksForWorkflowPath(startTask.getPath().getId()); - if(tasks.size()==1) - { - WorkflowTask task = tasks.get(0); - if(taskTypeMatches(task, taskTypes)) + // Deleting a person can cancel their invitations. Cancelling invitations can delete inactive persons! So prevent infinite looping here + if (TransactionalResourceHelper.getSet(getClass().getName()).add(startTask.getPath().getInstance().getId())) + { + List tasks = workflowService.getTasksForWorkflowPath(startTask.getPath().getId()); + if(tasks.size()==1) { - if(properties != null) + WorkflowTask task = tasks.get(0); + if(taskTypeMatches(task, taskTypes)) { - workflowService.updateTask(task.getId(), properties, null, null); + if(properties != null) + { + workflowService.updateTask(task.getId(), properties, null, null); + } + workflowService.endTask(task.getId(), transition); + return; } - workflowService.endTask(task.getId(), transition); - return; } + // Throw exception if the task not found. + Object objs[] = { startTask.getPath().getInstance().getId() }; + throw new InvitationExceptionUserError("invitation.invite.already_finished", objs); } - // Throw exception if the task not found. - Object objs[] = { startTask.getPath().getInstance().getId() }; - throw new InvitationExceptionUserError("invitation.invite.already_finished", objs); } /** @@ -476,11 +484,19 @@ public class InvitationServiceImpl implements InvitationService, NodeServicePoli { ModeratedInvitation invitation = getModeratedInvitation(startTask); String currentUserName = this.authenticationService.getCurrentUserName(); - if (false == currentUserName.equals(invitation.getInviteeUserName())) + if (!AuthenticationUtil.isRunAsUserTheSystemUser()) { - checkManagerRole(currentUserName, invitation.getResourceType(), invitation.getResourceName()); + if (false == currentUserName.equals(invitation.getInviteeUserName())) + { + checkManagerRole(currentUserName, invitation.getResourceType(), invitation.getResourceName()); + } + } + // Only proceed with the cancel if the site still exists (the site may have been deleted and invitations may be + // getting cancelled in the background) + if (this.siteService.getSite(invitation.getResourceName()) != null) + { + workflowService.cancelWorkflow(invitation.getInviteId()); } - workflowService.cancelWorkflow(invitation.getInviteId()); return invitation; } @@ -488,13 +504,21 @@ public class InvitationServiceImpl implements InvitationService, NodeServicePoli { NominatedInvitation invitation = getNominatedInvitation(startTask); String currentUserName = this.authenticationService.getCurrentUserName(); - if (false == currentUserName.equals(invitation.getInviterUserName())) + if (!AuthenticationUtil.isRunAsUserTheSystemUser()) { - checkManagerRole(currentUserName, invitation.getResourceType(), invitation.getResourceName()); + if (false == currentUserName.equals(invitation.getInviterUserName())) + { + checkManagerRole(currentUserName, invitation.getResourceType(), invitation.getResourceName()); + } + } + // Only proceed with the cancel if the site still exists (the site may have been deleted and invitations may be + // getting cancelled in the background) + if (this.siteService.getSite(invitation.getResourceName()) != null) + { + endInvitation(startTask, WorkflowModelNominatedInvitation.WF_TRANSITION_CANCEL, null, + WorkflowModelNominatedInvitation.WF_TASK_INVITE_PENDING, + WorkflowModelNominatedInvitation.WF_TASK_ACTIVIT_INVITE_PENDING); } - endInvitation(startTask, - WorkflowModelNominatedInvitation.WF_TRANSITION_CANCEL, null, - WorkflowModelNominatedInvitation.WF_TASK_INVITE_PENDING, WorkflowModelNominatedInvitation.WF_TASK_ACTIVIT_INVITE_PENDING); return invitation; } @@ -1468,11 +1492,26 @@ public class InvitationServiceImpl implements InvitationService, NodeServicePoli } } } + else if (dictionaryService.isSubClass(type, ContentModel.TYPE_PERSON)) + { + // this is a user being deleted. + String userName = (String) nodeService.getProperty(siteRef, ContentModel.PROP_USERNAME); + invalidateTasksByUser(userName); + } return null; } }, AuthenticationUtil.SYSTEM_USER_NAME); } + private void invalidateTasksByUser(String userName) throws AuthenticationException + { + List listForInvitee = listPendingInvitationsForInvitee(userName); + for (Invitation inv : listForInvitee) + { + cancel(inv.getInviteId()); + } + } + /** * Generates a description for the workflow * diff --git a/source/java/org/alfresco/repo/invitation/InviteHelper.java b/source/java/org/alfresco/repo/invitation/InviteHelper.java index b87dad16c5..af0a34cee5 100644 --- a/source/java/org/alfresco/repo/invitation/InviteHelper.java +++ b/source/java/org/alfresco/repo/invitation/InviteHelper.java @@ -39,6 +39,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.alfresco.model.ContentModel; import org.alfresco.repo.action.executer.MailActionExecuter; import org.alfresco.repo.i18n.MessageService; import org.alfresco.repo.invitation.site.InviteInfo; @@ -57,6 +58,7 @@ import org.alfresco.service.cmr.invitation.Invitation; import org.alfresco.service.cmr.invitation.InvitationExceptionForbidden; import org.alfresco.service.cmr.invitation.InvitationService; import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.TemplateService; import org.alfresco.service.cmr.security.MutableAuthenticationService; import org.alfresco.service.cmr.security.PersonService; @@ -112,6 +114,7 @@ public class InviteHelper implements InitializingBean private SiteService siteService; private TemplateService templateService; private WorkflowService workflowService; + private NodeService nodeService; private InviteSender inviteSender; @@ -125,6 +128,7 @@ public class InviteHelper implements InitializingBean this.siteService = serviceRegistry.getSiteService(); this.templateService = serviceRegistry.getTemplateService(); this.workflowService = serviceRegistry.getWorkflowService(); + this.nodeService = serviceRegistry.getNodeService(); this.inviteSender = new InviteSender(serviceRegistry, repositoryHelper, messageService); } @@ -139,7 +143,7 @@ public class InviteHelper implements InitializingBean { public Void doWork() throws Exception { - if (false==authenticationService.getAuthenticationEnabled(invitee)) + if (authenticationService.isAuthenticationMutable(invitee)) { authenticationService.setAuthenticationEnabled(invitee, true); } @@ -282,7 +286,7 @@ public class InviteHelper implements InitializingBean // if invitee's user account is still disabled and there are no pending invites outstanding // for the invitee, then remove the account and delete the invitee's person node - if ((authenticationService.authenticationExists(inviteeUserName)) + if ((authenticationService.isAuthenticationMutable(inviteeUserName)) && (authenticationService.getAuthenticationEnabled(inviteeUserName) == false) && (invitesPending == false)) { @@ -309,14 +313,17 @@ public class InviteHelper implements InitializingBean String inviteeUserName = (String) executionVariables.get(wfVarInviteeUserName); String siteShortName = (String) executionVariables.get(wfVarResourceName); - String currentUserName = authenticationService.getCurrentUserName(); - String currentUserSiteRole = siteService.getMembersRole(siteShortName, currentUserName); - if (SiteModel.SITE_MANAGER.equals(currentUserSiteRole)== false) + if (!AuthenticationUtil.isRunAsUserTheSystemUser()) { - // The current user is not the site manager - String inviteId = (String) executionVariables.get(wfVarWorkflowInstanceId); - Object[] args = {currentUserName, inviteId, siteShortName}; - throw new InvitationExceptionForbidden(MSG_NOT_SITE_MANAGER, args); + String currentUserName = authenticationService.getCurrentUserName(); + String currentUserSiteRole = siteService.getMembersRole(siteShortName, currentUserName); + if (SiteModel.SITE_MANAGER.equals(currentUserSiteRole)== false) + { + // The current user is not the site manager + String inviteId = (String) executionVariables.get(wfVarWorkflowInstanceId); + Object[] args = {currentUserName, inviteId, siteShortName}; + throw new InvitationExceptionForbidden(MSG_NOT_SITE_MANAGER, args); + } } // Clean up invitee's user account and person node if they are not in use i.e. @@ -395,7 +402,7 @@ public class InviteHelper implements InitializingBean // Send Action emailAction = actionService.createAction("mail"); - emailAction.setParameterValue(MailActionExecuter.PARAM_TO, inviteeUserName); + emailAction.setParameterValue(MailActionExecuter.PARAM_TO, nodeService.getProperty(personService.getPerson(inviteeUserName), ContentModel.PROP_EMAIL)); emailAction.setParameterValue(MailActionExecuter.PARAM_FROM, reviewer); //TODO Localize this. emailAction.setParameterValue(MailActionExecuter.PARAM_SUBJECT, "Rejected invitation to web site:" + resourceName); diff --git a/source/java/org/alfresco/repo/jscript/ScriptNode.java b/source/java/org/alfresco/repo/jscript/ScriptNode.java index 4412fbb5bc..0866c46437 100644 --- a/source/java/org/alfresco/repo/jscript/ScriptNode.java +++ b/source/java/org/alfresco/repo/jscript/ScriptNode.java @@ -26,10 +26,12 @@ import java.io.OutputStream; import java.io.Reader; import java.io.Serializable; import java.nio.charset.Charset; +import java.text.Collator; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.HashSet; @@ -112,6 +114,7 @@ import org.mozilla.javascript.ScriptableObject; import org.mozilla.javascript.UniqueTag; import org.mozilla.javascript.Wrapper; import org.springframework.extensions.surf.util.Content; +import org.springframework.extensions.surf.util.I18NUtil; import org.springframework.extensions.surf.util.ParameterCheck; import org.springframework.extensions.surf.util.URLEncoder; @@ -425,6 +428,10 @@ public class ScriptNode implements Scopeable, NamespacePrefixResolverProvider // create our Node representation from the NodeRef children[i] = newInstance(childRefs.get(i).getChildRef(), this.services, this.scope); } + + // Do a locale-sensitive sort by name + sort(children); + this.children = Context.getCurrentContext().newArray(this.scope, children); this.hasChildren = (children.length != 0); } @@ -432,6 +439,21 @@ public class ScriptNode implements Scopeable, NamespacePrefixResolverProvider return this.children; } + /** + * Performs a locale-sensitive sort by name of a node array + * @param nodes the node array + */ + private static void sort(Object[] nodes) + { + final Collator col = Collator.getInstance(I18NUtil.getLocale()); + Arrays.sort(nodes, new Comparator(){ + @Override + public int compare(Object o1, Object o2) + { + return col.compare(((ScriptNode)o1).getName(), ((ScriptNode)o2).getName()); + }}); + } + /** * @return true if the Node has children */ diff --git a/source/java/org/alfresco/repo/jscript/Search.java b/source/java/org/alfresco/repo/jscript/Search.java index 31e2324b29..5f8a1e5018 100644 --- a/source/java/org/alfresco/repo/jscript/Search.java +++ b/source/java/org/alfresco/repo/jscript/Search.java @@ -46,12 +46,11 @@ import org.apache.commons.logging.LogFactory; import org.dom4j.Document; import org.dom4j.Element; import org.dom4j.io.SAXReader; +import org.jaxen.saxpath.base.XPathReader; import org.mozilla.javascript.Context; import org.mozilla.javascript.Scriptable; import org.springframework.extensions.surf.util.ParameterCheck; -import com.werken.saxpath.XPathReader; - /** * Search component for use by the ScriptService. *

diff --git a/source/java/org/alfresco/repo/model/Repository.java b/source/java/org/alfresco/repo/model/Repository.java index e827085324..181b874d83 100644 --- a/source/java/org/alfresco/repo/model/Repository.java +++ b/source/java/org/alfresco/repo/model/Repository.java @@ -263,9 +263,12 @@ public class Repository implements ApplicationContextAware, ApplicationListener, { NodeRef person = null; String currentUserName = AuthenticationUtil.getRunAsUser(); - if (personService.personExists(currentUserName)) + if (currentUserName != null) { - person = personService.getPerson(currentUserName); + if (personService.personExists(currentUserName)) + { + person = personService.getPerson(currentUserName); + } } return person; } diff --git a/source/java/org/alfresco/repo/model/filefolder/FilenameFilteringInterceptor.java b/source/java/org/alfresco/repo/model/filefolder/FilenameFilteringInterceptor.java index 983b9ea3dc..5af9dd1718 100644 --- a/source/java/org/alfresco/repo/model/filefolder/FilenameFilteringInterceptor.java +++ b/source/java/org/alfresco/repo/model/filefolder/FilenameFilteringInterceptor.java @@ -19,6 +19,7 @@ package org.alfresco.repo.model.filefolder; import java.util.Iterator; +import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.repo.model.filefolder.HiddenAspect.Visibility; import org.alfresco.repo.security.authentication.AuthenticationUtil; @@ -110,10 +111,13 @@ public class FilenameFilteringInterceptor implements MethodInterceptor this.permissionService = permissionService; } - private void checkTemporaryAspect(boolean isTemporary, FileInfo fileInfo) + private void checkTemporaryAspect(boolean isTemporary, FileInfo fileInfo) + { + checkTemporaryAspect(isTemporary, fileInfo.getNodeRef()); + } + + private void checkTemporaryAspect(boolean isTemporary, NodeRef nodeRef) { - NodeRef nodeRef = fileInfo.getNodeRef(); - if(isTemporary) { // it matched, so apply the temporary and hidden aspects @@ -121,7 +125,7 @@ public class FilenameFilteringInterceptor implements MethodInterceptor if (logger.isDebugEnabled()) { - logger.debug("Applied temporary marker: " + fileInfo); + logger.debug("Applied temporary marker: " + nodeRef); } } else @@ -135,7 +139,7 @@ public class FilenameFilteringInterceptor implements MethodInterceptor if (logger.isDebugEnabled()) { - logger.debug("Removed temporary marker: " + fileInfo); + logger.debug("Removed temporary marker: " + nodeRef); } } } @@ -243,17 +247,37 @@ public class FilenameFilteringInterceptor implements MethodInterceptor else { ret = invocation.proceed(); + FileInfoImpl fileInfo = (FileInfoImpl)ret; checkTemporaryAspect(temporaryFiles.isFiltered(filename), fileInfo); } } - else if (methodName.startsWith("rename") || - methodName.startsWith("move") || - methodName.startsWith("copy")) + else if (methodName.startsWith("move")) + { + Object[] args = invocation.getArguments(); + NodeRef sourceNodeRef = (NodeRef)args[0]; + String newName = (String)args[args.length -1]; + + if(newName != null) + { + // Name is changing + // check against all the regular expressions + checkTemporaryAspect(temporaryFiles.isFiltered(newName), sourceNodeRef); + if(getMode() == Mode.ENHANCED) + { + hiddenAspect.checkHidden(sourceNodeRef, true); + } + } + + // now do the move + ret = invocation.proceed(); + } + else if (methodName.startsWith("copy")) { ret = invocation.proceed(); + FileInfoImpl fileInfo = (FileInfoImpl) ret; String filename = fileInfo.getName(); @@ -269,6 +293,46 @@ public class FilenameFilteringInterceptor implements MethodInterceptor { hiddenAspect.checkHidden(fileInfo, true); } + /* + * TODO should these two calls be before the proceed? However its the same problem as create + * The node needs to be created before we can add aspects. + */ + } + else if (methodName.startsWith("rename")) + { + Object[] args = invocation.getArguments(); + + if(args != null && args.length == 2) + { + /** + * Expecting rename(NodeRef, newName) + */ + String newName = (String)args[1]; + NodeRef sourceNodeRef = (NodeRef)args[0]; + + if (logger.isDebugEnabled()) + { + logger.debug("Checking filename returned by " + methodName + ": " + newName); + } + + // check against all the regular expressions + checkTemporaryAspect(temporaryFiles.isFiltered(newName), sourceNodeRef); + if(getMode() == Mode.ENHANCED) + { + hiddenAspect.checkHidden(sourceNodeRef, true); + } + + ret = invocation.proceed(); + + return ret; + } + else + { + /** + * expected rename(NodeRef, String) - got something else... + */ + throw new AlfrescoRuntimeException("FilenameFilteringInterceptor: unknown rename method"); + } } else { diff --git a/source/java/org/alfresco/repo/model/filefolder/HiddenAspect.java b/source/java/org/alfresco/repo/model/filefolder/HiddenAspect.java index 54a6c88e3c..b2580012b8 100644 --- a/source/java/org/alfresco/repo/model/filefolder/HiddenAspect.java +++ b/source/java/org/alfresco/repo/model/filefolder/HiddenAspect.java @@ -559,16 +559,6 @@ public class HiddenAspect return filter.pattern(); } - public Set getVisibility() - { - return clientVisibility; - } - - public Set getHiddenAttribute() - { - return hiddenAttribute; - } - public int getVisibilityMask() { return visibilityMask; diff --git a/source/java/org/alfresco/repo/node/cleanup/TransactionCleanupTest.java b/source/java/org/alfresco/repo/node/cleanup/TransactionCleanupTest.java new file mode 100644 index 0000000000..e3da0d993c --- /dev/null +++ b/source/java/org/alfresco/repo/node/cleanup/TransactionCleanupTest.java @@ -0,0 +1,242 @@ +package org.alfresco.repo.node.cleanup; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.domain.node.NodeDAO; +import org.alfresco.repo.domain.node.Transaction; +import org.alfresco.repo.node.db.DeletedNodeCleanupWorker; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.ServiceRegistry; +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.search.ResultSet; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.cmr.security.MutableAuthenticationService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.ApplicationContextHelper; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springframework.context.ApplicationContext; +import org.springframework.extensions.webscripts.GUID; + +public class TransactionCleanupTest +{ + private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); + + private TransactionService transactionService; + private NodeService nodeService; + private SearchService searchService; + private MutableAuthenticationService authenticationService; + private NodeDAO nodeDAO; + private DeletedNodeCleanupWorker worker; + + private NodeRef nodeRef1; + private NodeRef nodeRef2; + private NodeRef nodeRef3; + private NodeRef nodeRef4; + private NodeRef nodeRef5; + private RetryingTransactionHelper helper; + + @Before + public void before() + { + ServiceRegistry serviceRegistry = (ServiceRegistry)ctx.getBean("ServiceRegistry"); + this.transactionService = serviceRegistry.getTransactionService(); + this.authenticationService = (MutableAuthenticationService)ctx.getBean("authenticationService"); + this.nodeService = serviceRegistry.getNodeService(); + this.searchService = serviceRegistry.getSearchService(); + this.nodeDAO = (NodeDAO)ctx.getBean("nodeDAO"); + this.worker = (DeletedNodeCleanupWorker)ctx.getBean("nodeCleanup.deletedNodeCleanup"); + this.worker.setMinPurgeAgeDays(0); + + this.helper = transactionService.getRetryingTransactionHelper(); + authenticationService.authenticate("admin", "admin".toCharArray()); + + StoreRef storeRef = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore"); + ResultSet resultSet = searchService.query(storeRef, SearchService.LANGUAGE_LUCENE, "PATH:\"/app:company_home\""); + final NodeRef companyHome = resultSet.getNodeRef(0); + resultSet.close(); + + RetryingTransactionHelper.RetryingTransactionCallback createNode = new RetryingTransactionHelper.RetryingTransactionCallback() + { + @Override + public NodeRef execute() throws Throwable + { + return nodeService.createNode(companyHome, ContentModel.ASSOC_CONTAINS, QName.createQName("test", GUID.generate()), ContentModel.TYPE_CONTENT).getChildRef(); + } + }; + this.nodeRef1 = helper.doInTransaction(createNode, false, true); + this.nodeRef2 = helper.doInTransaction(createNode, false, true); + this.nodeRef3 = helper.doInTransaction(createNode, false, true); + this.nodeRef4 = helper.doInTransaction(createNode, false, true); + this.nodeRef5 = helper.doInTransaction(createNode, false, true); + } + + private Map> createTransactions() + { + Map> txnIds = new HashMap>(); + + UpdateNode updateNode1 = new UpdateNode(nodeRef1); + UpdateNode updateNode2 = new UpdateNode(nodeRef2); + UpdateNode updateNode3 = new UpdateNode(nodeRef3); + UpdateNode updateNode4 = new UpdateNode(nodeRef4); + UpdateNode updateNode5 = new UpdateNode(nodeRef5); + + List txnIds1 = new ArrayList(); + List txnIds2 = new ArrayList(); + List txnIds3 = new ArrayList(); + List txnIds4 = new ArrayList(); + List txnIds5 = new ArrayList(); + txnIds.put(nodeRef1, txnIds1); + txnIds.put(nodeRef2, txnIds2); + txnIds.put(nodeRef3, txnIds3); + txnIds.put(nodeRef4, txnIds4); + txnIds.put(nodeRef5, txnIds5); + + for(int i = 0; i < 10; i++) + { + String txnId1 = helper.doInTransaction(updateNode1, false, true); + txnIds1.add(txnId1); + if(i == 0) + { + String txnId2 = helper.doInTransaction(updateNode2, false, true); + txnIds2.add(txnId2); + } + if(i == 2) + { + String txnId3 = helper.doInTransaction(updateNode3, false, true); + txnIds3.add(txnId3); + } + if(i == 3) + { + String txnId4 = helper.doInTransaction(updateNode4, false, true); + txnIds4.add(txnId4); + } + if(i == 4) + { + String txnId5 = helper.doInTransaction(updateNode5, false, true); + txnIds5.add(txnId5); + } + } + + return txnIds; + } + + private boolean containsTransaction(List txns, String txnId) + { + boolean found = false; + for(Transaction tx : txns) + { + if(tx.getChangeTxnId().equals(txnId)) + { + found = true; + break; + } + } + return found; + } + + @Test + public void testPurgeUnusedTransactions() throws Exception + { + // Execute transactions that update a number of nodes. For nodeRef1, all but the last txn will be unused. + + // run the transaction cleaner to clean up any existing unused transactions + worker.doClean(); + + long start = System.currentTimeMillis(); + Long minTxnId = nodeDAO.getMinTxnId(); + + final Map> txnIds = createTransactions(); + final List txnIds1 = txnIds.get(nodeRef1); + final List txnIds2 = txnIds.get(nodeRef2); + final List txnIds3 = txnIds.get(nodeRef3); + final List txnIds4 = txnIds.get(nodeRef4); + final List txnIds5 = txnIds.get(nodeRef5); + + // run the transaction cleaner + worker.setPurgeSize(5); // small purge size + worker.doClean(); + + // Get transactions committed after the test started + List txns = nodeDAO.getTxnsByCommitTimeAscending(Long.valueOf(start), Long.valueOf(Long.MAX_VALUE), Integer.MAX_VALUE, null, false); + + List expectedUnusedTxnIds = new ArrayList(10); + expectedUnusedTxnIds.addAll(txnIds1.subList(0, txnIds1.size() - 1)); + + List expectedUsedTxnIds = new ArrayList(5); + expectedUsedTxnIds.add(txnIds1.get(txnIds1.size() - 1)); + expectedUsedTxnIds.addAll(txnIds2); + expectedUsedTxnIds.addAll(txnIds3); + expectedUsedTxnIds.addAll(txnIds4); + expectedUsedTxnIds.addAll(txnIds5); + + // check that the correct transactions have been purged i.e. all except the last one to update the node + // i.e. in this case, all but the last one in txnIds1 + int numFoundUnusedTxnIds = 0; + for(String txnId : expectedUnusedTxnIds) + { + if(!containsTransaction(txns, txnId)) + { + numFoundUnusedTxnIds++; + } + else if(txnIds1.contains(txnId)) + { + fail("Unused transaction(s) were not purged: " + txnId); + } + } + assertEquals(9, numFoundUnusedTxnIds); + + // check that the correct transactions remain i.e. all those in txnIds2, txnIds3, txnIds4 and txnIds5 + int numFoundUsedTxnIds = 0; + for(String txnId : expectedUsedTxnIds) + { + if(containsTransaction(txns, txnId)) + { + numFoundUsedTxnIds++; + } + } + + assertEquals(5, numFoundUsedTxnIds); + + List txnsUnused = nodeDAO.getTxnsUnused(minTxnId, Long.MAX_VALUE, Integer.MAX_VALUE); + assertEquals(0, txnsUnused.size()); + } + + @After + public void after() + { + ApplicationContextHelper.closeApplicationContext(); + } + + private class UpdateNode implements RetryingTransactionCallback + { + private NodeRef nodeRef; + + UpdateNode(NodeRef nodeRef) + { + this.nodeRef = nodeRef; + } + + @Override + public String execute() throws Throwable + { + nodeService.setProperty(nodeRef, ContentModel.PROP_NAME, GUID.generate()); + String txnId = AlfrescoTransactionSupport.getTransactionId(); + + return txnId; + } + }; +} diff --git a/source/java/org/alfresco/repo/node/db/DeletedNodeCleanupWorker.java b/source/java/org/alfresco/repo/node/db/DeletedNodeCleanupWorker.java index f06094ad53..86d95f79d7 100644 --- a/source/java/org/alfresco/repo/node/db/DeletedNodeCleanupWorker.java +++ b/source/java/org/alfresco/repo/node/db/DeletedNodeCleanupWorker.java @@ -22,10 +22,10 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import org.alfresco.repo.domain.node.NodeDAO; import org.alfresco.repo.node.cleanup.AbstractNodeCleanupWorker; import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; -import org.apache.commons.lang.mutable.MutableLong; /** * Cleans up deleted nodes and dangling transactions that are old enough. @@ -37,6 +37,10 @@ public class DeletedNodeCleanupWorker extends AbstractNodeCleanupWorker { private long minPurgeAgeMs; + // Unused transactions will be purged in chunks determined by commit time boundaries. 'index.tracking.purgeSize' specifies the size + // of the chunk (in ms). Default is a couple of hours. + private int purgeSize = 7200000; // ms + /** * Default constructor */ @@ -78,6 +82,16 @@ public class DeletedNodeCleanupWorker extends AbstractNodeCleanupWorker } /** + * Set the purge transaction block size. This determines how many unused transactions are purged in one go. + * + * @param txnBlockSize + */ + public void setPurgeSize(int purgeSize) + { + this.purgeSize = purgeSize; + } + + /** * Cleans up deleted nodes that are older than the given minimum age. * * @param minAge the minimum age of a transaction or deleted node @@ -139,7 +153,6 @@ public class DeletedNodeCleanupWorker extends AbstractNodeCleanupWorker return results; } - private static final int TXN_PURGE_BATCH_SIZE = 50; /** * Cleans up unused transactions that are older than the given minimum age. * @@ -153,50 +166,37 @@ public class DeletedNodeCleanupWorker extends AbstractNodeCleanupWorker return Collections.emptyList(); } final List results = new ArrayList(100); - final MutableLong minTxnId = new MutableLong(0L); final long maxCommitTime = System.currentTimeMillis() - minAge; - RetryingTransactionCallback purgeTxnsCallback = new RetryingTransactionCallback() - { - public Integer execute() throws Throwable - { - final List txnIds = nodeDAO.getTxnsUnused( - minTxnId.longValue(), - maxCommitTime, - TXN_PURGE_BATCH_SIZE); - for (Long txnId : txnIds) - { - nodeDAO.purgeTxn(txnId); - // Update the min node ID for the next query - if (txnId.longValue() > minTxnId.longValue()) - { - minTxnId.setValue(txnId.longValue()); - } - } - return txnIds.size(); - } - }; + long fromCommitTime = nodeDAO.getMinUnusedTxnCommitTime().longValue(); + + // delete unused transactions in batches of size 'purgeTxnBlockSize' while (true) { // Ensure we keep the lock refreshLock(); - + RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper(); txnHelper.setMaxRetries(5); // Limit number of retries txnHelper.setRetryWaitIncrementMs(1000); // 1 second to allow other cleanups time to get through - // Get nodes to delete - Integer purgeCount = new Integer(0); - // Purge nodes + + long toCommitTime = fromCommitTime + purgeSize; + if(toCommitTime >= maxCommitTime) + { + toCommitTime = maxCommitTime; + } + + // Purge transactions try { - purgeCount = txnHelper.doInTransaction(purgeTxnsCallback, false, true); - if (purgeCount.intValue() > 0) + DeleteTransactionsCallback purgeTxnsCallback = new DeleteTransactionsCallback(nodeDAO, fromCommitTime, toCommitTime); + long purgeCount = txnHelper.doInTransaction(purgeTxnsCallback, false, true); + if (purgeCount > 0) { String msg = "Purged old txns: \n" + - " Min txn ID: " + minTxnId.longValue() + "\n" + - " Batch size: " + TXN_PURGE_BATCH_SIZE + "\n" + - " Max commit time: " + maxCommitTime + "\n" + + " From commit time (ms): " + fromCommitTime + "\n" + + " To commit time (ms): " + toCommitTime + "\n" + " Purge count: " + purgeCount; results.add(msg); } @@ -206,9 +206,8 @@ public class DeletedNodeCleanupWorker extends AbstractNodeCleanupWorker String msg = "Failed to purge txns." + " Set log level to WARN for this class to get exception log: \n" + - " Min txn ID: " + minTxnId.longValue() + "\n" + - " Batch size: " + TXN_PURGE_BATCH_SIZE + "\n" + - " Max commit time: " + maxCommitTime + "\n" + + " From commit time: " + fromCommitTime + "\n" + + " To commit time (ms): " + toCommitTime + "\n" + " Error: " + e.getMessage(); // It failed; do a full log in WARN mode if (logger.isWarnEnabled()) @@ -222,12 +221,37 @@ public class DeletedNodeCleanupWorker extends AbstractNodeCleanupWorker results.add(msg); break; } - if (purgeCount.intValue() == 0) + + fromCommitTime += purgeSize; + if(fromCommitTime >= maxCommitTime) { - break; + break; } } // Done return results; } + + /* + * Delete a block of unused transactions + */ + private static class DeleteTransactionsCallback implements RetryingTransactionCallback + { + private NodeDAO nodeDAO; + private long fromCommitTime; + private long toCommitTime; + + DeleteTransactionsCallback(NodeDAO nodeDAO, long fromCommitTime, long toCommitTime) + { + this.nodeDAO = nodeDAO; + this.fromCommitTime = fromCommitTime; + this.toCommitTime = toCommitTime; + } + + public Long execute() throws Throwable + { + long count = nodeDAO.deleteTxnsUnused(fromCommitTime, toCommitTime); + return count; + } + } } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/node/index/AbstractReindexComponent.java b/source/java/org/alfresco/repo/node/index/AbstractReindexComponent.java index c276195615..bbcc3b1708 100644 --- a/source/java/org/alfresco/repo/node/index/AbstractReindexComponent.java +++ b/source/java/org/alfresco/repo/node/index/AbstractReindexComponent.java @@ -21,7 +21,7 @@ package org.alfresco.repo.node.index; import java.io.PrintWriter; import java.io.Serializable; import java.io.StringWriter; -import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -53,6 +53,7 @@ import org.alfresco.repo.transaction.TransactionServiceImpl; import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.InvalidNodeRefException; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; @@ -62,7 +63,6 @@ import org.alfresco.service.cmr.search.ResultSet; import org.alfresco.service.cmr.search.ResultSetRow; import org.alfresco.service.cmr.search.SearchParameters; import org.alfresco.service.cmr.search.SearchService; -import org.alfresco.util.Pair; import org.alfresco.util.ParameterCheck; import org.alfresco.util.PropertyCheck; import org.alfresco.util.VmShutdownListener; @@ -70,6 +70,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; +import org.springframework.dao.ConcurrencyFailureException; /** * Abstract helper for reindexing. @@ -278,6 +279,31 @@ public abstract class AbstractReindexComponent implements IndexRecovery * */ protected abstract void reindexImpl(); + + /** + * To allow for possible 'read committed' behaviour in some databases, where a node that previously existed during a + * transaction can disappear from existence, we treat InvalidNodeRefExceptions as concurrency conditions. + */ + protected T2 doInRetryingTransaction(final RetryingTransactionCallback callback, boolean isReadThrough) + { + return transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public T2 execute() throws Throwable + { + try + { + return callback.execute(); + } + catch (InvalidNodeRefException e) + { + // Turn InvalidNodeRefExceptions into retryable exceptions. + throw new ConcurrencyFailureException("Possible cache integrity issue during reindexing", e); + } + + } + }, true, isReadThrough); + } /** * If this object is currently busy, then it just nothing @@ -316,7 +342,7 @@ public abstract class AbstractReindexComponent implements IndexRecovery }; if (requireTransaction()) { - transactionService.getRetryingTransactionHelper().doInTransaction(reindexWork, true); + doInRetryingTransaction(reindexWork, false); } else { @@ -611,14 +637,14 @@ public abstract class AbstractReindexComponent implements IndexRecovery public InIndex isTxnPresentInIndex(final Transaction txn, final boolean readThrough) { - return transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + return doInRetryingTransaction(new RetryingTransactionCallback() { @Override public InIndex execute() throws Throwable { return isTxnPresentInIndex(txn); } - }, true, readThrough); + }, readThrough); } /** @@ -758,7 +784,7 @@ public abstract class AbstractReindexComponent implements IndexRecovery * * @throws ReindexTerminatedException if the VM is shutdown during the reindex */ - protected void reindexTransaction(final long txnId, ReindexNodeCallback callback, boolean isFull) + protected void reindexTransaction(final long txnId, final ReindexNodeCallback callback, final boolean isFull) { ParameterCheck.mandatory("txnId", txnId); if (logger.isDebugEnabled()) @@ -772,91 +798,153 @@ public abstract class AbstractReindexComponent implements IndexRecovery // The indexer will 'read through' to the latest database changes for the rest of this transaction indexer.setReadThrough(true); + + // Compile the complete set of transaction changes - we need to 'read through' here too + final Collection deletedNodes = new LinkedList(); + final Collection updatedNodes = new LinkedList(); + final Collection createdNodes = new LinkedList(); + final Collection deletedParents = new LinkedList(); + final Collection addedParents = new LinkedList(); - // get the node references pertinent to the transaction - We need to 'read through' here too - List> nodePairs = transactionService.getRetryingTransactionHelper().doInTransaction( - new RetryingTransactionCallback>>() - { - - @Override - public List> execute() throws Throwable - { - List nodeStatuses = nodeDAO.getTxnChanges(txnId); - List> nodePairs = new ArrayList>(nodeStatuses.size()); - for (NodeRef.Status nodeStatus : nodeStatuses) - { - ChildAssociationRef parent = nodeStatus.isDeleted() ? null : nodeService.getPrimaryParent(nodeStatus.getNodeRef()); - nodePairs.add(new Pair(nodeStatus, parent)); - } - return nodePairs; - } - }, true, true); - - // reindex each node - int nodeCount = 0; - for (Pair nodePair: nodePairs) + doInRetryingTransaction(new RetryingTransactionCallback() { - NodeRef.Status nodeStatus = nodePair.getFirst(); - NodeRef nodeRef = nodeStatus.getNodeRef(); - - if (nodeStatus.isDeleted()) // node deleted + @Override + public Void execute() throws Throwable { - if(isFull == false) + // process the node references pertinent to the transaction + List nodeStatuses = nodeDAO.getTxnChanges(txnId); + int nodeCount = 0; + for (NodeRef.Status nodeStatus : nodeStatuses) { - // only the child node ref is relevant - ChildAssociationRef assocRef = new ChildAssociationRef( - ContentModel.ASSOC_CHILDREN, - null, - null, - nodeRef); - indexer.deleteNode(assocRef); - if (logger.isDebugEnabled()) + NodeRef nodeRef = nodeStatus.getNodeRef(); + if (isFull) { - logger.debug("DELETE: " + nodeRef); - } - } - } - else // node created - { - if(isFull) - { - ChildAssociationRef assocRef = new ChildAssociationRef( - ContentModel.ASSOC_CHILDREN, - null, - null, - nodeRef); - indexer.createNode(assocRef); - if (logger.isDebugEnabled()) - { - logger.debug("CREATE: " + nodeRef); - } - } - else - { - // reindex - force a cascade reindex if possible (to account for a possible move) - ChildAssociationRef parent = nodePair.getSecond(); - if (parent == null) - { - indexer.updateNode(nodeRef); - if (logger.isDebugEnabled()) + if (!nodeStatus.isDeleted()) { - logger.debug("UPDATE: " + nodeRef); + createdNodes.add(new ChildAssociationRef(ContentModel.ASSOC_CHILDREN, null, null, nodeRef)); } } + else if (nodeStatus.isDeleted()) // node deleted + { + // only the child node ref is relevant + deletedNodes.add(new ChildAssociationRef(ContentModel.ASSOC_CHILDREN, null, null, nodeRef)); + } else { - indexer.createChildRelationship(parent); - if (logger.isDebugEnabled()) - { - logger.debug("MOVE: " + nodeRef + ", " + parent); - } + // Do a DB / index comparision to determine indexing operations required + indexer.detectNodeChanges(nodeRef, searcher, addedParents, deletedParents, createdNodes, + updatedNodes); + } + + // Check for VM shutdown every 100 nodes + if (++nodeCount % 100 == 0 && isShuttingDown()) + { + // We can't fail gracefully and run the risk of committing a half-baked transaction + logger.info("Reindexing of transaction " + txnId + " terminated by VM shutdown."); + throw new ReindexTerminatedException(); } } + return null; } + }, true); + + int nodeCount = 0; + for (ChildAssociationRef deletedNode: deletedNodes) + { + if (logger.isDebugEnabled()) + { + logger.debug("DELETE: " + deletedNode.getChildRef()); + } + indexer.deleteNode(deletedNode); + // Make the callback if (callback != null) { - callback.reindexedNode(nodeRef); + callback.reindexedNode(deletedNode.getChildRef()); + } + // Check for VM shutdown every 100 nodes + if (++nodeCount % 100 == 0 && isShuttingDown()) + { + // We can't fail gracefully and run the risk of committing a half-baked transaction + logger.info("Reindexing of transaction " + txnId + " terminated by VM shutdown."); + throw new ReindexTerminatedException(); + } + } + for (NodeRef updatedNode: updatedNodes) + { + if (logger.isDebugEnabled()) + { + logger.debug("UPDATE: " + updatedNode); + } + indexer.updateNode(updatedNode); + + // Make the callback + if (callback != null) + { + callback.reindexedNode(updatedNode); + } + // Check for VM shutdown every 100 nodes + if (++nodeCount % 100 == 0 && isShuttingDown()) + { + // We can't fail gracefully and run the risk of committing a half-baked transaction + logger.info("Reindexing of transaction " + txnId + " terminated by VM shutdown."); + throw new ReindexTerminatedException(); + } + } + for (ChildAssociationRef createdNode: createdNodes) + { + if (logger.isDebugEnabled()) + { + logger.debug("CREATE: " + createdNode.getChildRef()); + } + indexer.createNode(createdNode); + + // Make the callback + if (callback != null) + { + callback.reindexedNode(createdNode.getChildRef()); + } + // Check for VM shutdown every 100 nodes + if (++nodeCount % 100 == 0 && isShuttingDown()) + { + // We can't fail gracefully and run the risk of committing a half-baked transaction + logger.info("Reindexing of transaction " + txnId + " terminated by VM shutdown."); + throw new ReindexTerminatedException(); + } + } + for (ChildAssociationRef deletedParent: deletedParents) + { + if (logger.isDebugEnabled()) + { + logger.debug("UNLINK: " + deletedParent); + } + indexer.deleteChildRelationship(deletedParent); + + // Make the callback + if (callback != null) + { + callback.reindexedNode(deletedParent.getChildRef()); + } + // Check for VM shutdown every 100 nodes + if (++nodeCount % 100 == 0 && isShuttingDown()) + { + // We can't fail gracefully and run the risk of committing a half-baked transaction + logger.info("Reindexing of transaction " + txnId + " terminated by VM shutdown."); + throw new ReindexTerminatedException(); + } + } + for (ChildAssociationRef addedParent: addedParents) + { + if (logger.isDebugEnabled()) + { + logger.debug("LINK: " + addedParents); + } + indexer.createChildRelationship(addedParent); + + // Make the callback + if (callback != null) + { + callback.reindexedNode(addedParent.getChildRef()); } // Check for VM shutdown every 100 nodes if (++nodeCount % 100 == 0 && isShuttingDown()) @@ -999,7 +1087,7 @@ public abstract class AbstractReindexComponent implements IndexRecovery loggerOnThread.debug(msg); } // Do the work - transactionService.getRetryingTransactionHelper().doInTransaction(reindexCallback, true, true); + doInRetryingTransaction(reindexCallback, true); } catch (ReindexTerminatedException e) { @@ -1200,7 +1288,7 @@ public abstract class AbstractReindexComponent implements IndexRecovery return null; } }; - transactionService.getRetryingTransactionHelper().doInTransaction(reindexCallback, true, true); + doInRetryingTransaction(reindexCallback, true); return; } diff --git a/source/java/org/alfresco/repo/node/index/FullIndexRecoveryComponent.java b/source/java/org/alfresco/repo/node/index/FullIndexRecoveryComponent.java index cfe6d49d38..922f197253 100644 --- a/source/java/org/alfresco/repo/node/index/FullIndexRecoveryComponent.java +++ b/source/java/org/alfresco/repo/node/index/FullIndexRecoveryComponent.java @@ -19,6 +19,7 @@ package org.alfresco.repo.node.index; import java.util.ArrayList; +import java.util.Collections; import java.util.Date; import java.util.Iterator; import java.util.List; @@ -185,17 +186,12 @@ public class FullIndexRecoveryComponent extends AbstractReindexComponent transactionService.setAllowWrite(false, vetoName); } - - List startTxns = nodeDAO.getTxnsByCommitTimeAscending( - Long.MIN_VALUE, Long.MAX_VALUE, 1000, null, false); + List startTxns = getTxnsByCommitTimeWindowAscending(MIN_SAMPLE_TIME, 1000); InIndex startAllPresent = areTxnsInStartSample(startTxns); - - List endTxns = nodeDAO.getTxnsByCommitTimeDescending( - Long.MIN_VALUE, Long.MAX_VALUE, 1000, null, false); + List endTxns = getTxnsByCommitTimeWindowDescending(MIN_SAMPLE_TIME, 1000); InIndex endAllPresent = areAllTxnsInEndSample(endTxns); - - + // check the level of cover required switch (recoveryMode) { @@ -231,6 +227,135 @@ public class FullIndexRecoveryComponent extends AbstractReindexComponent } + /** + * List transactions up to the specified amount using a sliding time based window. + * This will create smaller result sets which circumvents performance problems using + * sql LIMIT on some jdbc drivers and databases. + * + * @param windowSize the size of collection window in milliseconds. + * @param count the number of transctions to attempt to collect + * @return returns a list of transactions + */ + private List getTxnsByCommitTimeWindowDescending(final long minWindowSize, final int count) { + if (minWindowSize == 0 || count == 0) + { + return Collections.emptyList(); + } + final long startTxns = nodeDAO.getMinTxnCommitTime(); + final long endTxns = nodeDAO.getMaxTxnCommitTime() + 1; + + List list = new ArrayList(count); + long toTimeExclusive = endTxns; + long window = minWindowSize; + //start at the end move backward by sample increment + while (true) + { + //slide window start backward by sample increment (windowsSize is a negative number) + long fromTimeInclusive = toTimeExclusive - window; + if (fromTimeInclusive <= startTxns) + { + //if we have moved back past the first transaction set to zero + fromTimeInclusive = startTxns; + } + List txns = nodeDAO.getTxnsByCommitTimeDescending( + fromTimeInclusive, toTimeExclusive, count, null, false); + + for (Transaction txn : txns) + { + list.add(txn); + //bail out if we have enough + if (list.size() >= count) break; + } + //bail out of main loop if we have enough or there are no more transactions + if (list.size() >= count || fromTimeInclusive == startTxns) + { + break; + } + //calculate new window size + if (list.size() == 0) + { + window = minWindowSize; + } + else + { + //calculate rate of transactions found (start to end of current window) + window = Math.max(minWindowSize, count + * (endTxns - fromTimeInclusive) / list.size()); + } + + //slide window end back to last inclusive time window + toTimeExclusive = fromTimeInclusive; + } + return list; + } + + /** + * List transactions up to the specified amount using a sliding time based window. + * This will create smaller result sets which circumvents performance problems using + * sql LIMIT on some jdbc drivers and databases. + * + * @param windowSize the size of collection window in milliseconds. + * @param count the number of transctions to attempt to collect + * @return returns a list of transactions + */ + private List getTxnsByCommitTimeWindowAscending(final long minWindowSize, final int count) { + if (minWindowSize == 0 || count == 0) + { + return Collections.emptyList(); + } + + //TODO: these return null for no transactions + final long startTxns = nodeDAO.getMinTxnCommitTime(); + final long endTxns = nodeDAO.getMaxTxnCommitTime() + 1; + + long window = minWindowSize; + List list = new ArrayList(count); + if ( window > 0) + { + long fromTimeInclusive = startTxns; + //start at the beginning move forward by sample increment + while (true) + { + //slide window end out from window start to window start + increment + long toTimeExclusive = fromTimeInclusive + window; + //if window size pushes window beyond end transaction then clip it to end + if (toTimeExclusive >= endTxns) { + toTimeExclusive = endTxns; + } + List txns = nodeDAO.getTxnsByCommitTimeAscending( + fromTimeInclusive, toTimeExclusive, count, null, false); + + for (Transaction txn : txns) + { + list.add(txn); + //bail out if we have enough + if (list.size() >= count) break; + } + //bail out of main loop if we have enough or there are no more transactions + if (list.size() >= count || toTimeExclusive >= endTxns) + { + break; + } + //calculate new window size + if (list.size() == 0) + { + window = minWindowSize; + } + else + { + //calculate rate of transactions found (start to end of current window) + window = Math.max(minWindowSize, count + * (toTimeExclusive - startTxns) / list.size()); + } + + //slide window start forward to last exclusive time window + fromTimeInclusive = toTimeExclusive; + } + } + return list; + } + + /** * @return Returns false if any one of the transactions aren't in the index. */ diff --git a/source/java/org/alfresco/repo/node/index/IndexTransactionTrackerTest.java b/source/java/org/alfresco/repo/node/index/IndexTransactionTrackerTest.java index eaaee714b6..150f1aff1a 100644 --- a/source/java/org/alfresco/repo/node/index/IndexTransactionTrackerTest.java +++ b/source/java/org/alfresco/repo/node/index/IndexTransactionTrackerTest.java @@ -29,6 +29,7 @@ import org.alfresco.repo.management.subsystems.ChildApplicationContextFactory; import org.alfresco.repo.search.Indexer; import org.alfresco.repo.search.impl.lucene.fts.FullTextSearchIndexer; import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.tenant.TenantService; import org.alfresco.repo.transaction.TransactionServiceImpl; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.ServiceRegistry; @@ -90,6 +91,7 @@ public class IndexTransactionTrackerTest extends TestCase indexTracker.setThreadPoolExecutor(threadPoolExecutor); indexTracker.setSearcher(searchService); indexTracker.setTransactionService((TransactionServiceImpl)transactionService); + indexTracker.setTenantService((TenantService) ctx.getBean("tenantService")); // authenticate authenticationComponent.setSystemUserAsCurrentUser(); diff --git a/source/java/org/alfresco/repo/search/Indexer.java b/source/java/org/alfresco/repo/search/Indexer.java index c21111b934..6068a2983d 100644 --- a/source/java/org/alfresco/repo/search/Indexer.java +++ b/source/java/org/alfresco/repo/search/Indexer.java @@ -18,9 +18,13 @@ */ package org.alfresco.repo.search; +import java.util.Collection; + +import org.alfresco.repo.search.impl.lucene.LuceneIndexException; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.search.SearchService; /** * This interface abstracts how indexing is used from within the node service @@ -103,6 +107,27 @@ public interface Indexer */ public void deleteChildRelationship(ChildAssociationRef relationshipRef); + /** + * Does a database vs index comparison for the given created/updated/renamed/referenced nodeRef in order to + * determine the set of indexing operations required + * + * @param nodeRef + * the nodeRef to process + * @param searcher + * searcher to query the indexes + * @param addedParents + * set to add new secondary parent associations to + * @param deletedParents + * set to add removed secondary parent associations to + * @param createdNodes + * set to add created nodes to + * @param updatedNodes + * set to add updated nodes to + */ + public void detectNodeChanges(NodeRef nodeRef, SearchService searcher, + Collection addedParents, Collection deletedParents, + Collection createdNodes, Collection updatedNodes); + /** * Delete the index for a store * @param storeRef diff --git a/source/java/org/alfresco/repo/search/IndexerComponent.java b/source/java/org/alfresco/repo/search/IndexerComponent.java index 50de7a0059..5b0ec5b1b9 100644 --- a/source/java/org/alfresco/repo/search/IndexerComponent.java +++ b/source/java/org/alfresco/repo/search/IndexerComponent.java @@ -18,11 +18,14 @@ */ package org.alfresco.repo.search; +import java.util.Collection; + import org.alfresco.repo.service.StoreRedirectorProxyFactory; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.search.SearchService; import org.springframework.extensions.surf.util.AbstractLifecycleBean; import org.springframework.context.ApplicationEvent; @@ -118,6 +121,14 @@ public class IndexerComponent extends AbstractLifecycleBean implements Indexer Indexer indexer = getIndexer(relationshipRef.getChildRef().getStoreRef()); indexer.deleteChildRelationship(relationshipRef); } + + public void detectNodeChanges(NodeRef nodeRef, SearchService searcher, + Collection addedParents, Collection deletedParents, + Collection createdNodes, Collection updatedNodes) + { + Indexer indexer = getIndexer(nodeRef.getStoreRef()); + indexer.detectNodeChanges(nodeRef, searcher, addedParents, deletedParents, createdNodes, updatedNodes); + } /* (non-Javadoc) * @see org.alfresco.repo.search.Indexer#deleteIndex(org.alfresco.service.cmr.repository.StoreRef) diff --git a/source/java/org/alfresco/repo/search/NodeServiceXPath.java b/source/java/org/alfresco/repo/search/NodeServiceXPath.java index faaf9b4155..312cb14a99 100644 --- a/source/java/org/alfresco/repo/search/NodeServiceXPath.java +++ b/source/java/org/alfresco/repo/search/NodeServiceXPath.java @@ -75,7 +75,6 @@ import org.jaxen.function.TrueFunction; import org.jaxen.function.ext.EndsWithFunction; import org.jaxen.function.ext.EvaluateFunction; import org.jaxen.function.ext.LowerFunction; -import org.jaxen.function.ext.MatrixConcatFunction; import org.jaxen.function.ext.UpperFunction; import org.jaxen.function.xslt.DocumentFunction; @@ -602,6 +601,9 @@ public class NodeServiceXPath extends BaseXPath registerFunction("", // namespace URI "document", new DocumentFunction()); + + registerFunction("", // namespace URI + "ends-with", new EndsWithFunction()); registerFunction("", // namespace URI "false", new FalseFunction()); @@ -673,9 +675,6 @@ public class NodeServiceXPath extends BaseXPath // extension functions should go into a namespace, but which one? // for now, keep them in default namespace to not break any code - registerFunction("", // namespace URI - "matrix-concat", new MatrixConcatFunction()); - registerFunction("", // namespace URI "evaluate", new EvaluateFunction()); diff --git a/source/java/org/alfresco/repo/search/impl/NoActionIndexer.java b/source/java/org/alfresco/repo/search/impl/NoActionIndexer.java index bb7f4dbdf1..3b98dbe428 100644 --- a/source/java/org/alfresco/repo/search/impl/NoActionIndexer.java +++ b/source/java/org/alfresco/repo/search/impl/NoActionIndexer.java @@ -18,10 +18,13 @@ */ package org.alfresco.repo.search.impl; +import java.util.Collection; + import org.alfresco.repo.search.Indexer; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.search.SearchService; /** * A no action indexer - the indexing is done automatically along with @@ -68,6 +71,13 @@ public class NoActionIndexer implements Indexer { return; } + + public void detectNodeChanges(NodeRef nodeRef, SearchService searcher, + Collection addedParents, Collection deletedParents, + Collection createdNodes, Collection updatedNodes) + { + return; + } /* (non-Javadoc) * @see org.alfresco.repo.search.Indexer#deleteIndex(org.alfresco.service.cmr.repository.StoreRef) diff --git a/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneIndexerImpl.java b/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneIndexerImpl.java index ebb01f8298..2054c48b30 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneIndexerImpl.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneIndexerImpl.java @@ -79,6 +79,10 @@ import org.alfresco.service.cmr.repository.TransformationOptions; import org.alfresco.service.cmr.repository.Path.ChildAssocElement; import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; import org.alfresco.service.cmr.repository.datatype.TypeConversionException; +import org.alfresco.service.cmr.search.ResultSet; +import org.alfresco.service.cmr.search.ResultSetRow; +import org.alfresco.service.cmr.search.SearchParameters; +import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.namespace.QName; import org.alfresco.util.CachingDateFormat; import org.alfresco.util.EqualsHelper; @@ -345,10 +349,10 @@ public class ADMLuceneIndexerImpl extends AbstractLuceneIndexerImpl imp throw new LuceneIndexException(event + " failed - node is not in the required store"); } final NodeRef parentRef = relationshipRef.getParentRef(); - final NodeRef childRef = relationshipRef.getChildRef(); - if (parentRef != null) + final NodeRef childRef = relationshipRef.getChildRef(); + if (parentRef != null && !relationshipRef.isPrimary()) { - // If the child has a lot of secondary parents, its cheaper to cascade reindex its parent + // If a SECONDARY child has a lot of secondary parents, its cheaper to cascade reindex the secondary parent // rather than itself if (doInReadthroughTransaction(new RetryingTransactionCallback() { @@ -648,6 +652,80 @@ public class ADMLuceneIndexerImpl extends AbstractLuceneIndexerImpl imp } } + public void detectNodeChanges(NodeRef nodeRef, SearchService searcher, + Collection addedParents, Collection deletedParents, + Collection createdNodes, Collection updatedNodes) throws LuceneIndexException + { + boolean nodeExisted = false; + boolean relationshipsChanged = false; + + ResultSet results = null; + SearchParameters sp = new SearchParameters(); + sp.setLanguage(SearchService.LANGUAGE_LUCENE); + sp.addStore(nodeRef.getStoreRef()); + try + { + sp.setQuery("ID:" + LuceneQueryParser.escape(nodeRef.toString())); + results = searcher.query(sp); + for (ResultSetRow row : results) + { + nodeExisted = true; + Document document = ((LuceneResultSetRow) row).getDocument(); + Field qname = document.getField("QNAME"); + if (qname == null) + { + continue; + } + Collection> allParents = getAllParents(nodeRef, nodeService.getProperties(nodeRef)); + Set dbParents = new HashSet(allParents.size() * 2); + for (Pair pair : allParents) + { + ChildAssociationRef qNameRef = tenantService.getName(pair.getFirst()); + if ((qNameRef != null) && (qNameRef.getParentRef() != null) && (qNameRef.getQName() != null)) + { + dbParents.add(new ChildAssociationRef(ContentModel.ASSOC_CHILDREN, qNameRef.getParentRef(), qNameRef.getQName(), qNameRef.getChildRef())); + } + } + + Field[] parents = document.getFields("PARENT"); + String[] qnames = qname.stringValue().split(";/"); + Set addedParentsSet = new HashSet(dbParents); + for (int i=0; i createDocumentsImpl(final String stringNodeRef, FTSStatus ftsStatus, boolean indexAllProperties, boolean includeDirectoryDocuments, final boolean cascade, final Set pathsProcessedSinceFlush, diff --git a/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneSearcherImpl.java b/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneSearcherImpl.java index 5eb003e7c0..40b4d0899d 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneSearcherImpl.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneSearcherImpl.java @@ -90,9 +90,8 @@ import org.apache.lucene.search.Query; import org.apache.lucene.search.Searcher; import org.apache.lucene.search.Sort; import org.apache.lucene.search.SortField; -import org.saxpath.SAXPathException; - -import com.werken.saxpath.XPathReader; +import org.jaxen.saxpath.SAXPathException; +import org.jaxen.saxpath.base.XPathReader; /** * The Lucene implementation of Searcher At the moment we support only lucene based queries. TODO: Support for other diff --git a/source/java/org/alfresco/repo/search/impl/lucene/AVMLuceneIndexerImpl.java b/source/java/org/alfresco/repo/search/impl/lucene/AVMLuceneIndexerImpl.java index 0cfdbfcb3c..c51e5352fe 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/AVMLuceneIndexerImpl.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/AVMLuceneIndexerImpl.java @@ -28,6 +28,7 @@ import java.io.UnsupportedEncodingException; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.HashSet; @@ -82,6 +83,7 @@ import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.repository.TransformationOptions; import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; import org.alfresco.service.cmr.repository.datatype.TypeConversionException; +import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.namespace.QName; import org.alfresco.util.CachingDateFormat; import org.alfresco.util.EqualsHelper; @@ -1591,6 +1593,14 @@ public class AVMLuceneIndexerImpl extends AbstractLuceneIndexerImpl impl throw new LuceneIndexException("Create node failed", e); } } + + @Override + public void detectNodeChanges(NodeRef nodeRef, SearchService searcher, + Collection addedParents, Collection deletedParents, + Collection createdNodes, Collection updatedNodes) + { + updatedNodes.add(nodeRef); + } public void updateNode(NodeRef nodeRef) { diff --git a/source/java/org/alfresco/repo/search/impl/lucene/DebugXPathHandler.java b/source/java/org/alfresco/repo/search/impl/lucene/DebugXPathHandler.java index 78f4355b27..0a6092a291 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/DebugXPathHandler.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/DebugXPathHandler.java @@ -18,11 +18,11 @@ */ package org.alfresco.repo.search.impl.lucene; -import org.saxpath.Axis; -import org.saxpath.SAXPathException; -import org.saxpath.XPathHandler; +import org.jaxen.saxpath.Axis; +import org.jaxen.saxpath.SAXPathException; +import org.jaxen.saxpath.XPathHandler; +import org.jaxen.saxpath.base.XPathReader; -import com.werken.saxpath.XPathReader; public class DebugXPathHandler implements XPathHandler { diff --git a/source/java/org/alfresco/repo/search/impl/lucene/LuceneAlfrescoXPathQueryLanguage.java b/source/java/org/alfresco/repo/search/impl/lucene/LuceneAlfrescoXPathQueryLanguage.java index 37ab6e92b2..4749d9da9c 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/LuceneAlfrescoXPathQueryLanguage.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/LuceneAlfrescoXPathQueryLanguage.java @@ -19,42 +19,17 @@ package org.alfresco.repo.search.impl.lucene; import java.io.IOException; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; import org.alfresco.repo.search.EmptyResultSet; import org.alfresco.repo.search.SearcherException; -import org.alfresco.repo.search.impl.parsers.AlfrescoFunctionEvaluationContext; -import org.alfresco.repo.search.impl.parsers.FTSParser; -import org.alfresco.repo.search.impl.parsers.FTSQueryParser; -import org.alfresco.repo.search.impl.querymodel.Argument; -import org.alfresco.repo.search.impl.querymodel.Column; -import org.alfresco.repo.search.impl.querymodel.Constraint; -import org.alfresco.repo.search.impl.querymodel.Function; -import org.alfresco.repo.search.impl.querymodel.Order; -import org.alfresco.repo.search.impl.querymodel.Ordering; -import org.alfresco.repo.search.impl.querymodel.QueryEngine; -import org.alfresco.repo.search.impl.querymodel.QueryEngineResults; -import org.alfresco.repo.search.impl.querymodel.QueryModelFactory; -import org.alfresco.repo.search.impl.querymodel.QueryOptions; -import org.alfresco.repo.search.impl.querymodel.QueryOptions.Connective; -import org.alfresco.repo.search.impl.querymodel.impl.functions.PropertyAccessor; -import org.alfresco.repo.search.impl.querymodel.impl.functions.Score; -import org.alfresco.repo.search.impl.querymodel.impl.lucene.LuceneOrdering; -import org.alfresco.service.cmr.search.LimitBy; import org.alfresco.service.cmr.search.ResultSet; import org.alfresco.service.cmr.search.SearchParameters; import org.alfresco.service.cmr.search.SearchService; -import org.alfresco.service.cmr.search.SearchParameters.SortDefinition; -import org.alfresco.service.cmr.search.SearchParameters.SortDefinition.SortType; import org.apache.lucene.search.Hits; import org.apache.lucene.search.Query; import org.apache.lucene.search.Searcher; -import org.saxpath.SAXPathException; - -import com.werken.saxpath.XPathReader; +import org.jaxen.saxpath.SAXPathException; +import org.jaxen.saxpath.base.XPathReader; /** * Alfresco FTS Query language support diff --git a/source/java/org/alfresco/repo/search/impl/lucene/index/IndexInfo.java b/source/java/org/alfresco/repo/search/impl/lucene/index/IndexInfo.java index 4b35b0aaac..74ad5bd4ef 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/index/IndexInfo.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/index/IndexInfo.java @@ -94,15 +94,15 @@ import org.apache.lucene.store.FSDirectory; import org.apache.lucene.store.IndexInput; import org.apache.lucene.store.IndexOutput; import org.apache.lucene.store.RAMDirectory; +import org.jaxen.saxpath.SAXPathException; +import org.jaxen.saxpath.base.XPathReader; import org.safehaus.uuid.UUID; -import org.saxpath.SAXPathException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.event.ContextRefreshedEvent; -import com.werken.saxpath.XPathReader; /** * The information that makes up an index. IndexInfoVersion Repeated information of the form diff --git a/source/java/org/alfresco/repo/search/impl/noindex/NoIndexIndexer.java b/source/java/org/alfresco/repo/search/impl/noindex/NoIndexIndexer.java index 078f8305b7..61ea5683eb 100644 --- a/source/java/org/alfresco/repo/search/impl/noindex/NoIndexIndexer.java +++ b/source/java/org/alfresco/repo/search/impl/noindex/NoIndexIndexer.java @@ -18,11 +18,14 @@ */ package org.alfresco.repo.search.impl.noindex; +import java.util.Collection; + import org.alfresco.error.StackTraceUtil; import org.alfresco.repo.search.Indexer; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.search.SearchService; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -127,6 +130,21 @@ public class NoIndexIndexer implements Indexer trace(); return; } + + /* (non-Javadoc) + * @see org.alfresco.repo.search.Indexer#detectNodeChanges(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.search.SearchService, java.util.Collection, java.util.Collection, java.util.Collection, java.util.Collection) + */ + @Override + public void detectNodeChanges(NodeRef nodeRef, SearchService searcher, + Collection addedParents, Collection deletedParents, + Collection createdNodes, Collection updatedNodes) + { + if(s_logger.isDebugEnabled()) + { + s_logger.debug("detectNodeChanges = "+nodeRef); + } + trace(); + } /* (non-Javadoc) * @see org.alfresco.repo.search.Indexer#deleteIndex(org.alfresco.service.cmr.repository.StoreRef) diff --git a/source/java/org/alfresco/repo/search/impl/solr/SolrQueryHTTPClient.java b/source/java/org/alfresco/repo/search/impl/solr/SolrQueryHTTPClient.java index d9528910d4..3f07d77fae 100644 --- a/source/java/org/alfresco/repo/search/impl/solr/SolrQueryHTTPClient.java +++ b/source/java/org/alfresco/repo/search/impl/solr/SolrQueryHTTPClient.java @@ -185,7 +185,7 @@ public class SolrQueryHTTPClient url.append(encoder.encode(searchParameters.getQuery(), "UTF-8")); url.append("&wt=").append(encoder.encode("json", "UTF-8")); - url.append("&fl=").append(encoder.encode("*,score", "UTF-8")); + url.append("&fl=").append(encoder.encode("DBID,score", "UTF-8")); if (searchParameters.getMaxItems() >= 0) { diff --git a/source/java/org/alfresco/repo/security/authentication/TicketCleanupJob.java b/source/java/org/alfresco/repo/security/authentication/TicketCleanupJob.java new file mode 100644 index 0000000000..0f37a3f2e5 --- /dev/null +++ b/source/java/org/alfresco/repo/security/authentication/TicketCleanupJob.java @@ -0,0 +1,55 @@ +/* + * 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 . + */ +package org.alfresco.repo.security.authentication; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.quartz.Job; +import org.quartz.JobDataMap; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; + +/** + * @author Andy + * + */ +public class TicketCleanupJob implements Job +{ + + public TicketCleanupJob() + { + } + + /** + * Calls the cleaner to do its work + */ + public void execute(JobExecutionContext context) throws JobExecutionException + { + JobDataMap jobData = context.getJobDetail().getJobDataMap(); + // extract the content cleaner to use + Object abstractAuthenticationServiceRef = jobData.get("abstractAuthenticationService"); + if (abstractAuthenticationServiceRef == null || !(abstractAuthenticationServiceRef instanceof AbstractAuthenticationService)) + { + throw new AlfrescoRuntimeException( + "ContentStoreCleanupJob data must contain valid 'contentStoreCleaner' reference"); + } + AbstractAuthenticationService abstractAuthenticationService = (AbstractAuthenticationService) abstractAuthenticationServiceRef; + abstractAuthenticationService.invalidateTickets(true); + } + +} diff --git a/source/java/org/alfresco/repo/security/permissions/impl/AbstractPermissionTest.java b/source/java/org/alfresco/repo/security/permissions/impl/AbstractPermissionTest.java index 1d96068cda..f24cb82ded 100644 --- a/source/java/org/alfresco/repo/security/permissions/impl/AbstractPermissionTest.java +++ b/source/java/org/alfresco/repo/security/permissions/impl/AbstractPermissionTest.java @@ -101,6 +101,8 @@ public class AbstractPermissionTest extends TestCase private UserTransaction testTX; + protected PermissionServiceImpl permissionServiceImpl; + public AbstractPermissionTest() { super(); @@ -120,6 +122,7 @@ public class AbstractPermissionTest extends TestCase dictionaryService = (DictionaryService) applicationContext.getBean(ServiceRegistry.DICTIONARY_SERVICE .getLocalName()); permissionService = (PermissionServiceSPI) applicationContext.getBean("permissionService"); + permissionServiceImpl = (PermissionServiceImpl) applicationContext.getBean("permissionServiceImpl"); namespacePrefixResolver = (NamespacePrefixResolver) applicationContext .getBean(ServiceRegistry.NAMESPACE_SERVICE.getLocalName()); authenticationService = (MutableAuthenticationService) applicationContext.getBean("authenticationService"); @@ -178,6 +181,8 @@ public class AbstractPermissionTest extends TestCase authenticationService.createAuthentication(AuthenticationUtil.getAdminUserName(), "admin".toCharArray()); authenticationComponent.clearCurrentSecurityContext(); + + assertTrue(permissionServiceImpl.getAnyDenyDenies()); } @Override diff --git a/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceImpl.java b/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceImpl.java index 24c217805d..f6f5dfd6c6 100644 --- a/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceImpl.java +++ b/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceImpl.java @@ -93,6 +93,8 @@ public class PermissionServiceImpl extends AbstractLifecycleBean implements Perm private SimpleCache accessCache; private SimpleCache> readersCache; + + private SimpleCache> readersDeniedCache; /* * Access to the model @@ -140,6 +142,8 @@ public class PermissionServiceImpl extends AbstractLifecycleBean implements Perm private PermissionReference allPermissionReference; + private boolean anyDenyDenies = false; + /** * Standard spring construction. */ @@ -152,6 +156,8 @@ public class PermissionServiceImpl extends AbstractLifecycleBean implements Perm // Inversion of control // + + /** * Set the dictionary service * @param dictionaryService @@ -161,6 +167,22 @@ public class PermissionServiceImpl extends AbstractLifecycleBean implements Perm this.dictionaryService = dictionaryService; } + /** + * @param anyDenyDenies the anyDenyDenies to set + */ + public void setAnyDenyDenies(boolean anyDenyDenies) + { + this.anyDenyDenies = anyDenyDenies; + accessCache.clear(); + readersCache.clear(); + readersDeniedCache.clear(); + } + + public boolean getAnyDenyDenies() + { + return anyDenyDenies; + } + /** * Set the permissions model dao * @@ -259,6 +281,15 @@ public class PermissionServiceImpl extends AbstractLifecycleBean implements Perm this.readersCache = readersCache; } + + /** + * @param readersDeniedCache the readersDeniedCache to set + */ + public void setReadersDeniedCache(SimpleCache> readersDeniedCache) + { + this.readersDeniedCache = readersDeniedCache; + } + /** * Set the policy component * @@ -1117,6 +1148,10 @@ public class PermissionServiceImpl extends AbstractLifecycleBean implements Perm private AccessStatus ownerRead(String username, NodeRef nodeRef) { + // Reviewed the behaviour of deny and ownership with Mike F + // ATM ownership takes precendence over READ deny + // TODO: check that global owner rights are set + AccessStatus result = AccessStatus.DENIED; String owner = ownableService.getOwner(nodeRef); @@ -1184,14 +1219,70 @@ public class PermissionServiceImpl extends AbstractLifecycleBean implements Perm return Collections.unmodifiableSet(readers); } + + /** + * @param aclId + * @return set of authorities with read permission on the ACL + */ + private Set buildReadersDenied(Long aclId) + { + HashSet assigned = new HashSet(); + HashSet denied = new HashSet(); + AccessControlList acl = aclDaoComponent.getAccessControlList(aclId); + + if (acl == null) + { + return denied; + } + + for (AccessControlEntry ace : acl.getEntries()) + { + assigned.add(ace.getAuthority()); + } + + for(String authority : assigned) + { + UnconditionalDeniedAclTest test = new UnconditionalDeniedAclTest(getPermissionReference(PermissionService.READ)); + if(test.evaluate(authority, aclId)) + { + denied.add(authority); + } + } + + return denied; + } private AccessStatus canRead(Long aclId) { Set authorities = getAuthorisations(); + // test denied + + if(anyDenyDenies) + { + + Set aclReadersDenied = readersDeniedCache.get(aclId); + if(aclReadersDenied == null) + { + aclReadersDenied = buildReadersDenied(aclId); + readersDeniedCache.put(aclId, aclReadersDenied); + } + + for(String auth : aclReadersDenied) + { + if(authorities.contains(auth)) + { + return AccessStatus.DENIED; + } + } + + } + + + // test acl readers Set aclReaders = getReaders(aclId); - + for(String auth : aclReaders) { if(authorities.contains(auth)) @@ -1210,6 +1301,8 @@ public class PermissionServiceImpl extends AbstractLifecycleBean implements Perm /** * Support class to test the permission on a node. * + * Not fixed up for deny as should not be used + * * @author Andy Hind */ private class NodeTest @@ -1658,6 +1751,29 @@ public class PermissionServiceImpl extends AbstractLifecycleBean implements Perm // any deny denies + if (anyDenyDenies) + { + if (denied != null) + { + for (String auth : authorisations) + { + Pair specific = new Pair(auth, required); + if (denied.contains(specific)) + { + return false; + } + for (PermissionReference perm : granters) + { + specific = new Pair(auth, perm); + if (denied.contains(specific)) + { + return false; + } + } + } + } + } + // If the permission has a match in both the authorities and // granters list it is allowed // It applies to the current user and it is granted @@ -1837,6 +1953,21 @@ public class PermissionServiceImpl extends AbstractLifecycleBean implements Perm return false; } + if(anyDenyDenies) + { + Set> allowed = new HashSet>(); + + // Check if each permission allows - the first wins. + // We could have other voting style mechanisms here + for (AccessControlEntry ace : acl.getEntries()) + { + if (isDenied(ace, authorisations, allowed, context)) + { + return false; + } + } + } + Set> denied = new HashSet>(); // Check if each permission allows - the first wins. @@ -1910,8 +2041,6 @@ public class PermissionServiceImpl extends AbstractLifecycleBean implements Perm } } - // any deny denies - // If the permission has a match in both the authorities and // granters list it is allowed // It applies to the current user and it is granted @@ -1926,6 +2055,81 @@ public class PermissionServiceImpl extends AbstractLifecycleBean implements Perm return false; } + /** + * Is a permission granted + * + * @param pe - + * the permissions entry to consider + * @param granters - + * the set of granters + * @param authorisations - + * the set of authorities + * @param denied - + * the set of denied permissions/authority pais + * @return true if granted + */ + private boolean isDenied(AccessControlEntry ace, Set authorisations, Set> allowed, PermissionContext context) + { + // If the permission entry denies then we just deny + if (ace.getAccessStatus() == AccessStatus.ALLOWED) + { + allowed.add(new Pair(ace.getAuthority(), ace.getPermission())); + + Set granters = modelDAO.getGrantingPermissions(ace.getPermission()); + for (PermissionReference granter : granters) + { + allowed.add(new Pair(ace.getAuthority(), granter)); + } + + // All the things granted by this permission must be + // denied + Set grantees = modelDAO.getGranteePermissions(ace.getPermission()); + for (PermissionReference grantee : grantees) + { + allowed.add(new Pair(ace.getAuthority(), grantee)); + } + + // All permission excludes all permissions available for + // the node. + if (ace.getPermission().equals(getAllPermissionReference()) || ace.getPermission().equals(OLD_ALL_PERMISSIONS_REFERENCE)) + { + for (PermissionReference deny : modelDAO.getAllPermissions(context.getType(), context.getAspects())) + { + allowed.add(new Pair(ace.getAuthority(), deny)); + } + } + + return false; + } + + // The permission is denied but we allow it as it is in the allowed + // set + + if (allowed != null) + { + Pair specific = new Pair(ace.getAuthority(), required); + if (allowed.contains(specific)) + { + return false; + } + } + + + // If the permission has a match in both the authorities and + // granters list it is allowed + // It applies to the current user and it is granted + if (authorisations.contains(ace.getAuthority()) && granters.contains(ace.getPermission())) + { + { + return true; + } + } + + // Default allow + return false; + } + + private boolean isGranted(PermissionEntry pe, Set authorisations) { // If the permission entry denies then we just deny @@ -2177,31 +2381,6 @@ public class PermissionServiceImpl extends AbstractLifecycleBean implements Perm } } - // any deny denies - -// if (false) -// { -// if (denied != null) -// { -// for (String auth : authorisations) -// { -// Pair specific = new Pair(auth, required); -// if (denied.contains(specific)) -// { -// return false; -// } -// for (PermissionReference perm : granters) -// { -// specific = new Pair(auth, perm); -// if (denied.contains(specific)) -// { -// return false; -// } -// } -// } -// } -// } - // If the permission has a match in both the authorities and // granters list it is allowed // It applies to the current user and it is granted @@ -2239,6 +2418,271 @@ public class PermissionServiceImpl extends AbstractLifecycleBean implements Perm } } + /** + * Ignores type and aspect requirements on the node + * + */ + private class UnconditionalDeniedAclTest + { + /* + * The required permission. + */ + PermissionReference required; + + /* + * Granters of the permission + */ + Set granters; + + /* + * The additional permissions required at the node level. + */ + Set nodeRequirements = new HashSet(); + + /* + * Constructor just gets the additional requirements + */ + UnconditionalDeniedAclTest(PermissionReference required) + { + this.required = required; + + // Set the required node permissions + if (required.equals(getPermissionReference(ALL_PERMISSIONS))) + { + nodeRequirements = modelDAO.getUnconditionalRequiredPermissions(getPermissionReference(PermissionService.FULL_CONTROL), RequiredPermission.On.NODE); + } + else + { + nodeRequirements = modelDAO.getUnconditionalRequiredPermissions(required, RequiredPermission.On.NODE); + } + + if (modelDAO.getUnconditionalRequiredPermissions(required, RequiredPermission.On.PARENT).size() > 0) + { + throw new IllegalStateException("Parent permissions can not be checked for an acl"); + } + + if (modelDAO.getUnconditionalRequiredPermissions(required, RequiredPermission.On.CHILDREN).size() > 0) + { + throw new IllegalStateException("Child permissions can not be checked for an acl"); + } + + // Find all the permissions that grant the allowed permission + // All permissions are treated specially. + granters = new LinkedHashSet(128, 1.0f); + granters.addAll(modelDAO.getGrantingPermissions(required)); + granters.add(getAllPermissionReference()); + granters.add(OLD_ALL_PERMISSIONS_REFERENCE); + } + + /** + * Internal hook point for recursion + * + * @param authorisations + * @param nodeRef + * @param denied + * @param recursiveIn + * @return true if granted + */ + boolean evaluate(String authority, Long aclId) + { + // Start out true and "and" all other results + boolean success = true; + + // Check the required permissions but not for sets they rely on + // their underlying permissions + //if (modelDAO.checkPermission(required)) + //{ + + // We have to do the test as no parent will help us out + success &= hasSinglePermission(authority, aclId); + + if (!success) + { + return false; + } + //} + + // Check the other permissions required on the node + for (PermissionReference pr : nodeRequirements) + { + // Build a new test + UnconditionalDeniedAclTest nt = new UnconditionalDeniedAclTest(pr); + success &= nt.evaluate(authority, aclId); + if (!success) + { + return false; + } + } + + return success; + } + + boolean hasSinglePermission(String authority, Long aclId) + { + // Check global permission + + if (checkGlobalPermissions(authority)) + { + return true; + } + + if(aclId == null) + { + return false; + } + else + { + return checkRequired(authority, aclId); + } + + } + + /** + * Check if we have a global permission + * + * @param authorisations + * @return true if granted + */ + private boolean checkGlobalPermissions(String authority) + { + for (PermissionEntry pe : modelDAO.getGlobalPermissionEntries()) + { + if (isDenied(pe, authority)) + { + return true; + } + } + return false; + } + + /** + * Check that a given authentication is available on a node + * + * @param authorisations + * @param nodeRef + * @param denied + * @return true if a check is required + */ + boolean checkRequired(String authority, Long aclId) + { + AccessControlList acl = aclDaoComponent.getAccessControlList(aclId); + + if (acl == null) + { + return false; + } + + Set> allowed = new HashSet>(); + + // Check if each permission allows - the first wins. + // We could have other voting style mechanisms here + for (AccessControlEntry ace : acl.getEntries()) + { + if (isDenied(ace, authority, allowed)) + { + return true; + } + } + return false; + } + + /** + * Is a permission granted + * + * @param pe - + * the permissions entry to consider + * @param granters - + * the set of granters + * @param authorisations - + * the set of authorities + * @param denied - + * the set of denied permissions/authority pais + * @return true if granted + */ + private boolean isDenied(AccessControlEntry ace, String authority, Set> allowed) + { + // If the permission entry denies then we just deny + if (ace.getAccessStatus() == AccessStatus.ALLOWED) + { + allowed.add(new Pair(ace.getAuthority(), ace.getPermission())); + + Set granters = modelDAO.getGrantingPermissions(ace.getPermission()); + for (PermissionReference granter : granters) + { + allowed.add(new Pair(ace.getAuthority(), granter)); + } + + // All the things granted by this permission must be + // denied + Set grantees = modelDAO.getGranteePermissions(ace.getPermission()); + for (PermissionReference grantee : grantees) + { + allowed.add(new Pair(ace.getAuthority(), grantee)); + } + + // All permission excludes all permissions available for + // the node. + if (ace.getPermission().equals(getAllPermissionReference()) || ace.getPermission().equals(OLD_ALL_PERMISSIONS_REFERENCE)) + { + for (PermissionReference deny : modelDAO.getAllPermissions()) + { + allowed.add(new Pair(ace.getAuthority(), deny)); + } + } + + return false; + } + + // The permission is allowed but we deny it as it is in the denied + // set + + if (allowed != null) + { + Pair specific = new Pair(ace.getAuthority(), required); + if (allowed.contains(specific)) + { + return false; + } + } + + // If the permission has a match in both the authorities and + // granters list it is allowed + // It applies to the current user and it is granted + if (authority.equals(ace.getAuthority()) && granters.contains(ace.getPermission())) + { + { + return true; + } + } + + // Default deny + return false; + } + + private boolean isDenied(PermissionEntry pe, String authority) + { + // If the permission entry denies then we just deny + if (pe.isAllowed()) + { + return false; + } + + // If the permission has a match in both the authorities and + // granters list it is allowed + // It applies to the current user and it is granted + if (granters.contains(pe.getPermissionReference()) && authority.equals(pe.getAuthority())) + { + { + return true; + } + } + + // Default deny + return false; + } + } + + private static class MutableBoolean { private boolean value; diff --git a/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceTest.java b/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceTest.java index 71476629fd..e48fd052a9 100644 --- a/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceTest.java +++ b/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceTest.java @@ -33,6 +33,7 @@ import org.alfresco.repo.security.permissions.ACLType; import org.alfresco.repo.security.permissions.AccessControlEntry; import org.alfresco.repo.security.permissions.AccessDeniedException; import org.alfresco.repo.security.permissions.PermissionEntry; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.security.AccessPermission; @@ -63,6 +64,288 @@ public class PermissionServiceTest extends AbstractPermissionTest // TODO Auto-generated constructor stub } + public void testAnyDenyDeniesAndRead() + { + personService.getPerson("andy"); + runAs("admin"); + + authorityService.createAuthority(AuthorityType.GROUP, "ONE"); + authorityService.addAuthority("GROUP_ONE", "andy"); + + authorityService.createAuthority(AuthorityType.GROUP, "TWO"); + authorityService.addAuthority("GROUP_TWO", "andy"); + + authorityService.createAuthority(AuthorityType.GROUP, "THREE"); + authorityService.addAuthority("GROUP_THREE", "andy"); + + authorityService.createAuthority(AuthorityType.GROUP, "GEN"); + authorityService.addAuthority("GROUP_GEN", "andy"); + + NodeRef one = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}one"), ContentModel.TYPE_FOLDER).getChildRef(); + permissionService.setPermission(one, "andy", PermissionService.READ, true); + permissionService.setPermission(one, "GROUP_ONE", PermissionService.READ, true); + + NodeRef two = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}two"), ContentModel.TYPE_FOLDER).getChildRef(); + permissionService.setPermission(two, "andy", PermissionService.READ, true); + permissionService.setPermission(two, "GROUP_TWO", PermissionService.READ, true); + + NodeRef three = nodeService.createNode(one, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}three"), ContentModel.TYPE_FOLDER).getChildRef(); + permissionService.setPermission(three, "andy", PermissionService.READ, true); + permissionService.setPermission(three, "GROUP_THREE", PermissionService.READ, true); + + NodeRef four = nodeService.createNode(three, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}four"), ContentModel.TYPE_FOLDER).getChildRef(); + + permissionServiceImpl.setAnyDenyDenies(false); + try + { + + + runAs("andy"); + assertEquals("andy", authenticationComponent.getCurrentUserName()); + assertTrue(permissionService.hasPermission(one, PermissionService.READ) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(two, PermissionService.READ) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(three, PermissionService.READ) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(four, PermissionService.READ) == AccessStatus.ALLOWED); + + assertTrue(permissionService.hasReadPermission(one) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasReadPermission(two) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasReadPermission(three) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasReadPermission(four) == AccessStatus.ALLOWED); + + // Deny group on one + runAs("admin"); + permissionService.setPermission(one, "GROUP_ONE", PermissionService.READ, false); + + runAs("andy"); + assertEquals("andy", authenticationComponent.getCurrentUserName()); + assertTrue(permissionService.hasPermission(one, PermissionService.READ) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(two, PermissionService.READ) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(three, PermissionService.READ) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(four, PermissionService.READ) == AccessStatus.ALLOWED); + + assertTrue(permissionService.hasReadPermission(one) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasReadPermission(two) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasReadPermission(three) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasReadPermission(four) == AccessStatus.ALLOWED); + + } + finally + { + // ANY DENY DENIES + + permissionServiceImpl.setAnyDenyDenies(true); + } + + runAs("andy"); + assertEquals("andy", authenticationComponent.getCurrentUserName()); + assertTrue(permissionService.hasPermission(one, PermissionService.READ) == AccessStatus.DENIED); + assertTrue(permissionService.hasPermission(two, PermissionService.READ) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(three, PermissionService.READ) == AccessStatus.DENIED); + assertTrue(permissionService.hasPermission(four, PermissionService.READ) == AccessStatus.DENIED); + + assertTrue(permissionService.hasReadPermission(one) == AccessStatus.DENIED); + assertTrue(permissionService.hasReadPermission(two) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasReadPermission(three) == AccessStatus.DENIED); + assertTrue(permissionService.hasReadPermission(four) == AccessStatus.DENIED); + + runAs("admin"); + permissionService.deletePermission(one, "GROUP_ONE", PermissionService.READ); + permissionService.setPermission(one, "GROUP_ONE", PermissionService.READ, true); + + // deny andy on one but explicitly allowed on three + + runAs("admin"); + permissionService.setPermission(one, "andy", PermissionService.READ, false); + + + runAs("andy"); + assertEquals("andy", authenticationComponent.getCurrentUserName()); + assertTrue(permissionService.hasPermission(one, PermissionService.READ) == AccessStatus.DENIED); + assertTrue(permissionService.hasPermission(two, PermissionService.READ) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(three, PermissionService.READ) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(four, PermissionService.READ) == AccessStatus.ALLOWED); + + assertTrue(permissionService.hasReadPermission(one) == AccessStatus.DENIED); + assertTrue(permissionService.hasReadPermission(two) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasReadPermission(three) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasReadPermission(four) == AccessStatus.ALLOWED); + + runAs("admin"); + permissionService.deletePermission(one, "andy", PermissionService.READ); + permissionService.setPermission(one, "andy", PermissionService.READ, true); + + // Deny by all - the underlying allow should win + + runAs("admin"); + permissionService.setPermission(one, "andy", PermissionService.ALL_PERMISSIONS, false); + + runAs("andy"); + assertEquals("andy", authenticationComponent.getCurrentUserName()); + assertTrue(permissionService.hasPermission(one, PermissionService.READ) == AccessStatus.DENIED); + assertTrue(permissionService.hasPermission(two, PermissionService.READ) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(three, PermissionService.READ) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(four, PermissionService.READ) == AccessStatus.ALLOWED); + + assertTrue(permissionService.hasReadPermission(one) == AccessStatus.DENIED); + assertTrue(permissionService.hasReadPermission(two) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasReadPermission(three) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasReadPermission(four) == AccessStatus.ALLOWED); + + runAs("admin"); + permissionService.deletePermission(one, "andy", PermissionService.ALL_PERMISSIONS); + + // Deny by all - should win as no underlying mask + + runAs("admin"); + permissionService.setPermission(one, "GROUP_GEN", PermissionService.ALL_PERMISSIONS, false); + + runAs("andy"); + assertEquals("andy", authenticationComponent.getCurrentUserName()); + assertTrue(permissionService.hasPermission(one, PermissionService.READ) == AccessStatus.DENIED); + assertTrue(permissionService.hasPermission(two, PermissionService.READ) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(three, PermissionService.READ) == AccessStatus.DENIED); + assertTrue(permissionService.hasPermission(four, PermissionService.READ) == AccessStatus.DENIED); + + assertTrue(permissionService.hasReadPermission(one) == AccessStatus.DENIED); + assertTrue(permissionService.hasReadPermission(two) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasReadPermission(three) == AccessStatus.DENIED); + assertTrue(permissionService.hasReadPermission(four) == AccessStatus.DENIED); + + runAs("admin"); + permissionService.setInheritParentPermissions(three, false); + + runAs("andy"); + assertEquals("andy", authenticationComponent.getCurrentUserName()); + assertTrue(permissionService.hasPermission(one, PermissionService.READ) == AccessStatus.DENIED); + assertTrue(permissionService.hasPermission(two, PermissionService.READ) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(three, PermissionService.READ) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(four, PermissionService.READ) == AccessStatus.ALLOWED); + + assertTrue(permissionService.hasReadPermission(one) == AccessStatus.DENIED); + assertTrue(permissionService.hasReadPermission(two) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasReadPermission(three) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasReadPermission(four) == AccessStatus.ALLOWED); + + runAs("admin"); + permissionService.setInheritParentPermissions(three, true); + permissionService.deletePermission(one, "GROUP_GEN", PermissionService.ALL_PERMISSIONS); + + // direct deny + + runAs("admin"); + permissionService.setPermission(three, "andy", PermissionService.READ, false); + + runAs("andy"); + assertEquals("andy", authenticationComponent.getCurrentUserName()); + assertTrue(permissionService.hasPermission(one, PermissionService.READ) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(two, PermissionService.READ) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(three, PermissionService.READ) == AccessStatus.DENIED); + assertTrue(permissionService.hasPermission(four, PermissionService.READ) == AccessStatus.DENIED); + + assertTrue(permissionService.hasReadPermission(one) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasReadPermission(two) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasReadPermission(three) == AccessStatus.DENIED); + assertTrue(permissionService.hasReadPermission(four) == AccessStatus.DENIED); + + runAs("admin"); + permissionService.deletePermission(three, "andy", PermissionService.READ); + permissionService.setPermission(three, "andy", PermissionService.READ, true); + + permissionService.setPermission(one, "GROUP_ONE", PermissionService.READ, false); + permissionService.setPermission(two, "GROUP_TWO", PermissionService.READ, false); + permissionService.setPermission(three, "GROUP_THREE", PermissionService.READ, false); + + runAs("andy"); + assertEquals("andy", authenticationComponent.getCurrentUserName()); + assertTrue(permissionService.hasPermission(one, PermissionService.READ) == AccessStatus.DENIED); + assertTrue(permissionService.hasPermission(two, PermissionService.READ) == AccessStatus.DENIED); + assertTrue(permissionService.hasPermission(three, PermissionService.READ) == AccessStatus.DENIED); + assertTrue(permissionService.hasPermission(four, PermissionService.READ) == AccessStatus.DENIED); + + assertTrue(permissionService.hasReadPermission(one) == AccessStatus.DENIED); + assertTrue(permissionService.hasReadPermission(two) == AccessStatus.DENIED); + assertTrue(permissionService.hasReadPermission(three) == AccessStatus.DENIED); + assertTrue(permissionService.hasReadPermission(four) == AccessStatus.DENIED); + + runAs("admin"); + authorityService.removeAuthority("GROUP_ONE", "andy"); + AlfrescoTransactionSupport.bindResource("MyAuthCache", null); + + runAs("andy"); + assertEquals("andy", authenticationComponent.getCurrentUserName()); + assertTrue(permissionService.hasPermission(one, PermissionService.READ) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(two, PermissionService.READ) == AccessStatus.DENIED); + assertTrue(permissionService.hasPermission(three, PermissionService.READ) == AccessStatus.DENIED); + assertTrue(permissionService.hasPermission(four, PermissionService.READ) == AccessStatus.DENIED); + + assertTrue(permissionService.hasReadPermission(one) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasReadPermission(two) == AccessStatus.DENIED); + assertTrue(permissionService.hasReadPermission(three) == AccessStatus.DENIED); + assertTrue(permissionService.hasReadPermission(four) == AccessStatus.DENIED); + + runAs("admin"); + authorityService.removeAuthority("GROUP_TWO", "andy"); + AlfrescoTransactionSupport.bindResource("MyAuthCache", null); + + runAs("andy"); + assertEquals("andy", authenticationComponent.getCurrentUserName()); + assertTrue(permissionService.hasPermission(one, PermissionService.READ) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(two, PermissionService.READ) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(three, PermissionService.READ) == AccessStatus.DENIED); + assertTrue(permissionService.hasPermission(four, PermissionService.READ) == AccessStatus.DENIED); + + assertTrue(permissionService.hasReadPermission(one) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasReadPermission(two) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasReadPermission(three) == AccessStatus.DENIED); + assertTrue(permissionService.hasReadPermission(four) == AccessStatus.DENIED); + + runAs("admin"); + authorityService.removeAuthority("GROUP_THREE", "andy"); + AlfrescoTransactionSupport.bindResource("MyAuthCache", null); + + runAs("andy"); + assertEquals("andy", authenticationComponent.getCurrentUserName()); + assertTrue(permissionService.hasPermission(one, PermissionService.READ) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(two, PermissionService.READ) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(three, PermissionService.READ) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(four, PermissionService.READ) == AccessStatus.ALLOWED); + + assertTrue(permissionService.hasReadPermission(one) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasReadPermission(two) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasReadPermission(three) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasReadPermission(four) == AccessStatus.ALLOWED); + + runAs("admin"); + permissionService.setPermission(one, PermissionService.ALL_AUTHORITIES, PermissionService.READ, false); + + runAs("andy"); + assertEquals("andy", authenticationComponent.getCurrentUserName()); + assertTrue(permissionService.hasPermission(one, PermissionService.READ) == AccessStatus.DENIED); + assertTrue(permissionService.hasPermission(two, PermissionService.READ) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(three, PermissionService.READ) == AccessStatus.DENIED); + assertTrue(permissionService.hasPermission(four, PermissionService.READ) == AccessStatus.DENIED); + + assertTrue(permissionService.hasReadPermission(one) == AccessStatus.DENIED); + assertTrue(permissionService.hasReadPermission(two) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasReadPermission(three) == AccessStatus.DENIED); + assertTrue(permissionService.hasReadPermission(four) == AccessStatus.DENIED); + + runAs("admin"); + permissionService.setPermission(rootNodeRef, PermissionService.ALL_AUTHORITIES, PermissionService.READ, false); + + runAs("andy"); + assertEquals("andy", authenticationComponent.getCurrentUserName()); + assertTrue(permissionService.hasPermission(one, PermissionService.READ) == AccessStatus.DENIED); + assertTrue(permissionService.hasPermission(two, PermissionService.READ) == AccessStatus.DENIED); + assertTrue(permissionService.hasPermission(three, PermissionService.READ) == AccessStatus.DENIED); + assertTrue(permissionService.hasPermission(four, PermissionService.READ) == AccessStatus.DENIED); + + assertTrue(permissionService.hasReadPermission(one) == AccessStatus.DENIED); + assertTrue(permissionService.hasReadPermission(two) == AccessStatus.DENIED); + assertTrue(permissionService.hasReadPermission(three) == AccessStatus.DENIED); + assertTrue(permissionService.hasReadPermission(four) == AccessStatus.DENIED); + } + /* * Tests that the current user is contained in the current authorisations set */ @@ -83,243 +366,243 @@ public class PermissionServiceTest extends AbstractPermissionTest NodeRef two = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}two"), ContentModel.TYPE_FOLDER).getChildRef(); permissionService.setPermission(two, "andy", PermissionService.WRITE, true); NodeRef three = nodeService.createNode(one, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}two"), ContentModel.TYPE_FOLDER).getChildRef(); - - + + NodeRef test = nodeService.createNode(one, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}test"), ContentModel.TYPE_FOLDER).getChildRef(); - + // test has shared acl - - + + // under 1 // start runAs("andy"); assertEquals("andy", authenticationComponent.getCurrentUserName()); assertTrue(permissionService.hasPermission(test, PermissionService.READ) == AccessStatus.ALLOWED); assertTrue(permissionService.hasPermission(test, PermissionService.WRITE) == AccessStatus.DENIED); - + //under 2 // def parent -> def parent runAs("admin"); nodeService.moveNode(test, two, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}test")); assertEquals(two, nodeService.getPrimaryParent(test).getParentRef()); - + runAs("andy"); assertEquals("andy", authenticationComponent.getCurrentUserName()); assertTrue(permissionService.hasPermission(test, PermissionService.READ) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(test, PermissionService.WRITE) == AccessStatus.ALLOWED); - + // under 3 // def parent -> shared parent runAs("admin"); nodeService.moveNode(test, three, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}test")); assertEquals(three, nodeService.getPrimaryParent(test).getParentRef()); - + runAs("andy"); assertEquals("andy", authenticationComponent.getCurrentUserName()); assertTrue(permissionService.hasPermission(test, PermissionService.READ) == AccessStatus.ALLOWED); assertTrue(permissionService.hasPermission(test, PermissionService.WRITE) == AccessStatus.DENIED); - + //under 2 // shared parent -> def parent runAs("admin"); nodeService.moveNode(test, two, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}test")); assertEquals(two, nodeService.getPrimaryParent(test).getParentRef()); - + runAs("andy"); assertEquals("andy", authenticationComponent.getCurrentUserName()); assertTrue(permissionService.hasPermission(test, PermissionService.READ) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(test, PermissionService.WRITE) == AccessStatus.ALLOWED); - + //under 1 // def parent -> def parent runAs("admin"); nodeService.moveNode(test, one, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}test")); assertEquals(one, nodeService.getPrimaryParent(test).getParentRef()); - + runAs("andy"); assertEquals("andy", authenticationComponent.getCurrentUserName()); assertTrue(permissionService.hasPermission(test, PermissionService.READ) == AccessStatus.ALLOWED); assertTrue(permissionService.hasPermission(test, PermissionService.WRITE) == AccessStatus.DENIED); - - + + // test has defining acl - + runAs("admin"); permissionService.setPermission(test, "andy", PermissionService.CHANGE_PERMISSIONS, true); - + runAs("andy"); assertEquals("andy", authenticationComponent.getCurrentUserName()); assertTrue(permissionService.hasPermission(test, PermissionService.READ) == AccessStatus.ALLOWED); assertTrue(permissionService.hasPermission(test, PermissionService.WRITE) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(test, PermissionService.CHANGE_PERMISSIONS) == AccessStatus.ALLOWED); - + // 2 runAs("admin"); nodeService.moveNode(test, two, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}test")); assertEquals(two, nodeService.getPrimaryParent(test).getParentRef()); - + runAs("andy"); assertEquals("andy", authenticationComponent.getCurrentUserName()); assertTrue(permissionService.hasPermission(test, PermissionService.READ) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(test, PermissionService.WRITE) == AccessStatus.ALLOWED); assertTrue(permissionService.hasPermission(test, PermissionService.CHANGE_PERMISSIONS) == AccessStatus.ALLOWED); - + // 3 runAs("admin"); nodeService.moveNode(test, three, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}test")); assertEquals(three, nodeService.getPrimaryParent(test).getParentRef()); - + runAs("andy"); assertEquals("andy", authenticationComponent.getCurrentUserName()); assertTrue(permissionService.hasPermission(test, PermissionService.READ) == AccessStatus.ALLOWED); assertTrue(permissionService.hasPermission(test, PermissionService.WRITE) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(test, PermissionService.CHANGE_PERMISSIONS) == AccessStatus.ALLOWED); - + // 2 runAs("admin"); nodeService.moveNode(test, two, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}test")); assertEquals(two, nodeService.getPrimaryParent(test).getParentRef()); - + runAs("andy"); assertEquals("andy", authenticationComponent.getCurrentUserName()); assertTrue(permissionService.hasPermission(test, PermissionService.READ) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(test, PermissionService.WRITE) == AccessStatus.ALLOWED); assertTrue(permissionService.hasPermission(test, PermissionService.CHANGE_PERMISSIONS) == AccessStatus.ALLOWED); - + // 1 runAs("admin"); nodeService.moveNode(test, one, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}test")); assertEquals(one, nodeService.getPrimaryParent(test).getParentRef()); - + runAs("andy"); assertEquals("andy", authenticationComponent.getCurrentUserName()); assertTrue(permissionService.hasPermission(test, PermissionService.READ) == AccessStatus.ALLOWED); assertTrue(permissionService.hasPermission(test, PermissionService.WRITE) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(test, PermissionService.CHANGE_PERMISSIONS) == AccessStatus.ALLOWED); - + // 1 inherit - + runAs("admin"); permissionService.setInheritParentPermissions(test, true); - + runAs("andy"); assertEquals("andy", authenticationComponent.getCurrentUserName()); assertTrue(permissionService.hasPermission(test, PermissionService.READ) == AccessStatus.ALLOWED); assertTrue(permissionService.hasPermission(test, PermissionService.WRITE) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(test, PermissionService.CHANGE_PERMISSIONS) == AccessStatus.ALLOWED); - + runAs("admin"); permissionService.setInheritParentPermissions(test, false); - + runAs("andy"); assertEquals("andy", authenticationComponent.getCurrentUserName()); assertTrue(permissionService.hasPermission(test, PermissionService.READ) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(test, PermissionService.WRITE) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(test, PermissionService.CHANGE_PERMISSIONS) == AccessStatus.ALLOWED); - + runAs("admin"); permissionService.setInheritParentPermissions(test, true); - + runAs("andy"); assertEquals("andy", authenticationComponent.getCurrentUserName()); assertTrue(permissionService.hasPermission(test, PermissionService.READ) == AccessStatus.ALLOWED); assertTrue(permissionService.hasPermission(test, PermissionService.WRITE) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(test, PermissionService.CHANGE_PERMISSIONS) == AccessStatus.ALLOWED); - + // 2 inherit - + runAs("admin"); nodeService.moveNode(test, two, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}test")); assertEquals(two, nodeService.getPrimaryParent(test).getParentRef()); - + runAs("andy"); assertEquals("andy", authenticationComponent.getCurrentUserName()); assertTrue(permissionService.hasPermission(test, PermissionService.READ) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(test, PermissionService.WRITE) == AccessStatus.ALLOWED); assertTrue(permissionService.hasPermission(test, PermissionService.CHANGE_PERMISSIONS) == AccessStatus.ALLOWED); - - + + runAs("admin"); permissionService.setInheritParentPermissions(test, true); - + runAs("andy"); assertEquals("andy", authenticationComponent.getCurrentUserName()); assertTrue(permissionService.hasPermission(test, PermissionService.READ) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(test, PermissionService.WRITE) == AccessStatus.ALLOWED); assertTrue(permissionService.hasPermission(test, PermissionService.CHANGE_PERMISSIONS) == AccessStatus.ALLOWED); - + runAs("admin"); permissionService.setInheritParentPermissions(test, false); - + runAs("andy"); assertEquals("andy", authenticationComponent.getCurrentUserName()); assertTrue(permissionService.hasPermission(test, PermissionService.READ) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(test, PermissionService.WRITE) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(test, PermissionService.CHANGE_PERMISSIONS) == AccessStatus.ALLOWED); - + runAs("admin"); permissionService.setInheritParentPermissions(test, true); - + runAs("andy"); assertEquals("andy", authenticationComponent.getCurrentUserName()); assertTrue(permissionService.hasPermission(test, PermissionService.READ) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(test, PermissionService.WRITE) == AccessStatus.ALLOWED); assertTrue(permissionService.hasPermission(test, PermissionService.CHANGE_PERMISSIONS) == AccessStatus.ALLOWED); - + // 3 inherit - + runAs("admin"); nodeService.moveNode(test, three, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}test")); assertEquals(three, nodeService.getPrimaryParent(test).getParentRef()); - + runAs("andy"); assertEquals("andy", authenticationComponent.getCurrentUserName()); assertTrue(permissionService.hasPermission(test, PermissionService.READ) == AccessStatus.ALLOWED); assertTrue(permissionService.hasPermission(test, PermissionService.WRITE) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(test, PermissionService.CHANGE_PERMISSIONS) == AccessStatus.ALLOWED); - + runAs("admin"); permissionService.setInheritParentPermissions(test, true); - + runAs("andy"); assertEquals("andy", authenticationComponent.getCurrentUserName()); assertTrue(permissionService.hasPermission(test, PermissionService.READ) == AccessStatus.ALLOWED); assertTrue(permissionService.hasPermission(test, PermissionService.WRITE) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(test, PermissionService.CHANGE_PERMISSIONS) == AccessStatus.ALLOWED); - + runAs("admin"); permissionService.setInheritParentPermissions(test, false); - + runAs("andy"); assertEquals("andy", authenticationComponent.getCurrentUserName()); assertTrue(permissionService.hasPermission(test, PermissionService.READ) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(test, PermissionService.WRITE) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(test, PermissionService.CHANGE_PERMISSIONS) == AccessStatus.ALLOWED); - + // move to 2 without inherit - + runAs("admin"); nodeService.moveNode(test, two, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}test")); assertEquals(two, nodeService.getPrimaryParent(test).getParentRef()); - + runAs("andy"); assertEquals("andy", authenticationComponent.getCurrentUserName()); assertTrue(permissionService.hasPermission(test, PermissionService.READ) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(test, PermissionService.WRITE) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(test, PermissionService.CHANGE_PERMISSIONS) == AccessStatus.ALLOWED); - + // move to 3 without inherit - + runAs("admin"); nodeService.moveNode(test, three, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}test")); assertEquals(three, nodeService.getPrimaryParent(test).getParentRef()); - + runAs("andy"); assertEquals("andy", authenticationComponent.getCurrentUserName()); assertTrue(permissionService.hasPermission(test, PermissionService.READ) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(test, PermissionService.WRITE) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(test, PermissionService.CHANGE_PERMISSIONS) == AccessStatus.ALLOWED); - + } public void testChangePersonUid() @@ -357,7 +640,7 @@ public class PermissionServiceTest extends AbstractPermissionTest } } assertTrue(found); - + try { nodeService.setProperty(andy, ContentModel.PROP_USERNAME, "Bob"); @@ -708,7 +991,7 @@ public class PermissionServiceTest extends AbstractPermissionTest assertEquals("andy", AuthenticationUtil.getRunAsUser()); AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() - { + { public Object doWork() throws Exception { assertEquals("andy", AuthenticationUtil.getFullyAuthenticatedUser()); @@ -720,7 +1003,7 @@ public class PermissionServiceTest extends AbstractPermissionTest assertEquals(AuthenticationUtil.getAdminUserName(), AuthenticationUtil.getRunAsUser()); AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() - { + { public Object doWork() throws Exception { @@ -730,7 +1013,7 @@ public class PermissionServiceTest extends AbstractPermissionTest assertEquals("lemur", AuthenticationUtil.getRunAsUser()); AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() - { + { public Object doWork() throws Exception { @@ -740,7 +1023,7 @@ public class PermissionServiceTest extends AbstractPermissionTest assertEquals(AuthenticationUtil.getAdminUserName(), AuthenticationUtil.getRunAsUser()); AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() - { + { public Object doWork() throws Exception { @@ -751,28 +1034,28 @@ public class PermissionServiceTest extends AbstractPermissionTest return null; } - }, "andy"); + }, "andy"); assertEquals("andy", AuthenticationUtil.getFullyAuthenticatedUser()); assertEquals(AuthenticationUtil.getAdminUserName(), AuthenticationUtil.getRunAsUser()); return null; } - }, AuthenticationUtil.getAdminUserName()); + }, AuthenticationUtil.getAdminUserName()); assertEquals("andy", AuthenticationUtil.getFullyAuthenticatedUser()); assertEquals("lemur", AuthenticationUtil.getRunAsUser()); return null; } - }, "lemur"); + }, "lemur"); assertEquals("andy", AuthenticationUtil.getFullyAuthenticatedUser()); assertEquals(AuthenticationUtil.getAdminUserName(), AuthenticationUtil.getRunAsUser()); return null; } - }, AuthenticationUtil.getAdminUserName()); + }, AuthenticationUtil.getAdminUserName()); assertEquals("andy", AuthenticationUtil.getFullyAuthenticatedUser()); assertEquals("andy", AuthenticationUtil.getRunAsUser()); @@ -792,7 +1075,7 @@ public class PermissionServiceTest extends AbstractPermissionTest assertEquals("andy", AuthenticationUtil.getRunAsUser()); AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() - { + { public Object doWork() throws Exception { @@ -802,7 +1085,7 @@ public class PermissionServiceTest extends AbstractPermissionTest assertEquals(AuthenticationUtil.getAdminUserName(), AuthenticationUtil.getRunAsUser()); AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() - { + { public Object doWork() throws Exception { @@ -812,7 +1095,7 @@ public class PermissionServiceTest extends AbstractPermissionTest assertEquals("lemur", AuthenticationUtil.getRunAsUser()); AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() - { + { public Object doWork() throws Exception { @@ -822,7 +1105,7 @@ public class PermissionServiceTest extends AbstractPermissionTest assertEquals(AuthenticationUtil.getAdminUserName(), AuthenticationUtil.getRunAsUser()); AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() - { + { public Object doWork() throws Exception { @@ -833,28 +1116,28 @@ public class PermissionServiceTest extends AbstractPermissionTest return null; } - }, "andy"); + }, "andy"); assertEquals("andy", AuthenticationUtil.getFullyAuthenticatedUser()); assertEquals(AuthenticationUtil.getAdminUserName(), AuthenticationUtil.getRunAsUser()); return null; } - }, AuthenticationUtil.getAdminUserName()); + }, AuthenticationUtil.getAdminUserName()); assertEquals("andy", AuthenticationUtil.getFullyAuthenticatedUser()); assertEquals("lemur", AuthenticationUtil.getRunAsUser()); return null; } - }, "lemur"); + }, "lemur"); assertEquals("andy", AuthenticationUtil.getFullyAuthenticatedUser()); assertEquals(AuthenticationUtil.getAdminUserName(), AuthenticationUtil.getRunAsUser()); return null; } - }, AuthenticationUtil.getAdminUserName()); + }, AuthenticationUtil.getAdminUserName()); assertEquals("andy", AuthenticationUtil.getFullyAuthenticatedUser()); assertEquals("andy", AuthenticationUtil.getRunAsUser()); @@ -873,7 +1156,7 @@ public class PermissionServiceTest extends AbstractPermissionTest assertNull(AuthenticationUtil.getRunAsUser()); AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() - { + { public Object doWork() throws Exception { @@ -883,7 +1166,7 @@ public class PermissionServiceTest extends AbstractPermissionTest assertEquals(AuthenticationUtil.getAdminUserName(), AuthenticationUtil.getRunAsUser()); return null; } - }, AuthenticationUtil.getAdminUserName()); + }, AuthenticationUtil.getAdminUserName()); assertNull(AuthenticationUtil.getFullyAuthenticatedUser()); assertNull(AuthenticationUtil.getRunAsUser()); @@ -901,7 +1184,7 @@ public class PermissionServiceTest extends AbstractPermissionTest assertNull(AuthenticationUtil.getRunAsUser()); AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() - { + { public Object doWork() throws Exception { @@ -911,7 +1194,7 @@ public class PermissionServiceTest extends AbstractPermissionTest assertEquals(AuthenticationUtil.getAdminUserName(), AuthenticationUtil.getRunAsUser()); AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() - { + { public Object doWork() throws Exception { @@ -921,7 +1204,7 @@ public class PermissionServiceTest extends AbstractPermissionTest assertEquals("lemur", AuthenticationUtil.getRunAsUser()); AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() - { + { public Object doWork() throws Exception { @@ -931,7 +1214,7 @@ public class PermissionServiceTest extends AbstractPermissionTest assertEquals(AuthenticationUtil.getAdminUserName(), AuthenticationUtil.getRunAsUser()); AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() - { + { public Object doWork() throws Exception { @@ -942,28 +1225,28 @@ public class PermissionServiceTest extends AbstractPermissionTest return null; } - }, "andy"); + }, "andy"); assertEquals(AuthenticationUtil.getAdminUserName(), AuthenticationUtil.getFullyAuthenticatedUser()); assertEquals(AuthenticationUtil.getAdminUserName(), AuthenticationUtil.getRunAsUser()); return null; } - }, AuthenticationUtil.getAdminUserName()); + }, AuthenticationUtil.getAdminUserName()); assertEquals(AuthenticationUtil.getAdminUserName(), AuthenticationUtil.getFullyAuthenticatedUser()); assertEquals("lemur", AuthenticationUtil.getRunAsUser()); return null; } - }, "lemur"); + }, "lemur"); assertEquals(AuthenticationUtil.getAdminUserName(), AuthenticationUtil.getFullyAuthenticatedUser()); assertEquals(AuthenticationUtil.getAdminUserName(), AuthenticationUtil.getRunAsUser()); return null; } - }, AuthenticationUtil.getAdminUserName()); + }, AuthenticationUtil.getAdminUserName()); assertNull(AuthenticationUtil.getFullyAuthenticatedUser()); assertNull(AuthenticationUtil.getRunAsUser()); @@ -1042,7 +1325,7 @@ public class PermissionServiceTest extends AbstractPermissionTest runAs(AuthenticationUtil.getAdminUserName()); NodeRef n1 = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}one"), ContentModel.TYPE_FOLDER).getChildRef(); - + permissionService.setPermission(new SimplePermissionEntry(n1, getPermission(PermissionService.READ), "andy", AccessStatus.ALLOWED)); permissionService.setPermission(new SimplePermissionEntry(n1, getPermission(PermissionService.READ), "Andy", AccessStatus.ALLOWED)); permissionService.setPermission(new SimplePermissionEntry(n1, getPermission(PermissionService.READ), "ANDY", AccessStatus.ALLOWED)); @@ -1051,11 +1334,11 @@ public class PermissionServiceTest extends AbstractPermissionTest permissionService.setPermission(new SimplePermissionEntry(n1, getPermission(PermissionService.READ), "woof/ADOBE", AccessStatus.ALLOWED)); permissionService.setPermission(new SimplePermissionEntry(n1, getPermission(PermissionService.READ), "Woof/Adobe", AccessStatus.ALLOWED)); permissionService.setPermission(new SimplePermissionEntry(n1, getPermission(PermissionService.READ), "WOOF/ADOBE", AccessStatus.ALLOWED)); - + assertEquals(8, permissionService.getAllSetPermissions(n1).size()); } - - + + public void testGetAllSetPermissions() { runAs("andy"); @@ -1109,15 +1392,15 @@ public class PermissionServiceTest extends AbstractPermissionTest NodeRef n1 = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}one"), ContentModel.TYPE_FOLDER).getChildRef(); NodeRef n2 = nodeService.createNode(n1, ContentModel.ASSOC_CONTAINS, QName.createQName("{namespace}two"), ContentModel.TYPE_FOLDER).getChildRef(); - + runAs("andy"); - + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(n1, getPermission(PermissionService.READ)) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.DENIED); - + runAs(AuthenticationUtil.getAdminUserName()); - + permissionService.setPermission(new SimplePermissionEntry(n1, getPermission(PermissionService.READ), "andy", AccessStatus.ALLOWED)); runAs("andy"); @@ -1943,16 +2226,16 @@ public class PermissionServiceTest extends AbstractPermissionTest long start; long end; long time = 0; -// for (int i = 0; i < 1000; i++) -// { -// getSession().flush(); -// // getSession().clear(); -// start = System.nanoTime(); -// assertTrue(permissionService.hasPermission(n10, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); -// end = System.nanoTime(); -// time += (end - start); -// } -// System.out.println("Time is " + (time / 1000000000.0)); + // for (int i = 0; i < 1000; i++) + // { + // getSession().flush(); + // // getSession().clear(); + // start = System.nanoTime(); + // assertTrue(permissionService.hasPermission(n10, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + // end = System.nanoTime(); + // time += (end - start); + // } + // System.out.println("Time is " + (time / 1000000000.0)); // assertTrue((time / 1000000000.0) < 60.0); time = 0; @@ -2313,8 +2596,8 @@ public class PermissionServiceTest extends AbstractPermissionTest permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES), "andy", AccessStatus.DENIED)); runAs("andy"); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.DENIED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); runAs("lemur"); @@ -2325,8 +2608,8 @@ public class PermissionServiceTest extends AbstractPermissionTest permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ_CHILDREN), "andy", AccessStatus.ALLOWED)); runAs("andy"); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.DENIED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.DENIED); assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); runAs("lemur"); @@ -2337,10 +2620,10 @@ public class PermissionServiceTest extends AbstractPermissionTest permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ), "andy", AccessStatus.DENIED)); runAs("andy"); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.DENIED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.DENIED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.DENIED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.DENIED); runAs("lemur"); assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); @@ -2348,6 +2631,89 @@ public class PermissionServiceTest extends AbstractPermissionTest assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); } + public void testGroupAndUserInteractionAnyAllowAllows() + { + + permissionServiceImpl.setAnyDenyDenies(false); + try + { + runAs("andy"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ), "andy", AccessStatus.ALLOWED)); + runAs("andy"); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ), ROLE_AUTHENTICATED, AccessStatus.ALLOWED)); + runAs("andy"); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES), "andy", AccessStatus.DENIED)); + runAs("andy"); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ_CHILDREN), "andy", AccessStatus.ALLOWED)); + runAs("andy"); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ), "andy", AccessStatus.DENIED)); + runAs("andy"); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + } + finally + { + permissionServiceImpl.setAnyDenyDenies(true); + } + } + public void testInheritPermissions() { runAs(AuthenticationUtil.getAdminUserName()); @@ -2770,7 +3136,7 @@ public class PermissionServiceTest extends AbstractPermissionTest assertEquals(0, permissionService.getAllSetPermissions(rootNodeRef).size()); } - + public void xtestAclInsertionPerformanceShared() { NodeRef parent = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}one"), ContentModel.TYPE_FOLDER).getChildRef(); @@ -2781,10 +3147,10 @@ public class PermissionServiceTest extends AbstractPermissionTest long start = System.nanoTime(); permissionService.setPermission(new SimplePermissionEntry(parent, getPermission(PermissionService.CONSUMER), "andy", AccessStatus.ALLOWED)); long end = System.nanoTime(); - + assertTrue("Time was "+(end - start)/1000000000.0f, end == start); } - + public void xtestAclInsertionPerformanceDefining() { NodeRef parent = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}one"), ContentModel.TYPE_FOLDER).getChildRef(); @@ -2904,7 +3270,7 @@ public class PermissionServiceTest extends AbstractPermissionTest // assertEquals(1, permissionService.getAllSetPermissionsForAuthority("andy").get(n10).size()); } - + public void test_DefiningShared_AclUpdatePerformance() { runAs("admin"); @@ -2928,7 +3294,7 @@ public class PermissionServiceTest extends AbstractPermissionTest //assertTrue("Time was "+(end - start)/1000000000.0f, end == start); } - + public void test_DefiningDefining_AclUpdatePerformance() { runAs("admin"); @@ -2943,7 +3309,7 @@ public class PermissionServiceTest extends AbstractPermissionTest permissionService.setPermission(two, "andy", PermissionService.WRITE, true); NodeRef test = nodeService.createNode(one, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}test"), ContentModel.TYPE_FOLDER).getChildRef(); permissionService.setPermission(test, "andy", PermissionService.CHANGE_PERMISSIONS, true); - + // test has shared acl @@ -2956,10 +3322,10 @@ public class PermissionServiceTest extends AbstractPermissionTest long end = System.nanoTime(); //assertTrue("Time was "+(end - start)/1000000000.0f, end == start); - + } - + public void testAclInsertionPerformanceShared() { NodeRef parent = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}one"), ContentModel.TYPE_FOLDER).getChildRef(); @@ -2970,10 +3336,10 @@ public class PermissionServiceTest extends AbstractPermissionTest long start = System.nanoTime(); permissionService.setPermission(new SimplePermissionEntry(parent, getPermission(PermissionService.CONSUMER), "andy", AccessStatus.ALLOWED)); long end = System.nanoTime(); - + //assertTrue("Time was "+(end - start)/1000000000.0f, end == start); } - + public void testAclInsertionPerformanceDefining() { NodeRef parent = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}one"), ContentModel.TYPE_FOLDER).getChildRef(); @@ -3006,7 +3372,7 @@ public class PermissionServiceTest extends AbstractPermissionTest //assertTrue("Time was "+(end - start)/1000000000.0f, end == start); } - + public void xtestFindNodesByPermission() { diff --git a/source/java/org/alfresco/repo/security/permissions/impl/acegi/ACLEntryVoter.java b/source/java/org/alfresco/repo/security/permissions/impl/acegi/ACLEntryVoter.java index b59c3407c3..3397359f3e 100644 --- a/source/java/org/alfresco/repo/security/permissions/impl/acegi/ACLEntryVoter.java +++ b/source/java/org/alfresco/repo/security/permissions/impl/acegi/ACLEntryVoter.java @@ -57,6 +57,8 @@ public class ACLEntryVoter implements AccessDecisionVoter, InitializingBean private static Log log = LogFactory.getLog(ACLEntryVoter.class); private static final String ACL_NODE = "ACL_NODE"; + + private static final String ACL_PRI_CHILD_ASSOC_ON_CHILD = "ACL_PRI_CHILD_ASSOC_ON_CHILD"; private static final String ACL_PARENT = "ACL_PARENT"; @@ -205,6 +207,7 @@ public class ACLEntryVoter implements AccessDecisionVoter, InitializingBean { if ((attribute.getAttribute() != null) && (attribute.getAttribute().startsWith(ACL_NODE) + || attribute.getAttribute().startsWith(ACL_PRI_CHILD_ASSOC_ON_CHILD) || attribute.getAttribute().startsWith(ACL_PARENT) || attribute.getAttribute().equals(ACL_ALLOW) || attribute.getAttribute().startsWith(ACL_METHOD) @@ -256,7 +259,7 @@ public class ACLEntryVoter implements AccessDecisionVoter, InitializingBean MethodInvocation invocation = (MethodInvocation) object; Method method = invocation.getMethod(); - Class[] params = method.getParameterTypes(); + Class[] params = method.getParameterTypes(); Boolean hasMethodEntry = null; @@ -288,30 +291,95 @@ public class ACLEntryVoter implements AccessDecisionVoter, InitializingBean hasMethodEntry = Boolean.TRUE; } } - else if (cad.parameter >= invocation.getArguments().length) + else if (cad.typeString.equals(ACL_PRI_CHILD_ASSOC_ON_CHILD)) { - continue; + if (cad.parameter.length == 2 && NodeRef.class.isAssignableFrom(params[cad.parameter[0]]) + && NodeRef.class.isAssignableFrom(params[cad.parameter[1]])) + { + testNodeRef = getArgument(invocation, cad.parameter[1]); + if (testNodeRef != null) + { + if (nodeService.exists(testNodeRef)) + { + if (log.isDebugEnabled()) + { + log.debug("\tPermission test on node " + nodeService.getPath(testNodeRef)); + } + ChildAssociationRef primaryParent = nodeService.getPrimaryParent(testNodeRef); + NodeRef testParentNodeRef = getArgument(invocation, cad.parameter[0]); + if (primaryParent == null || testParentNodeRef == null + || !testParentNodeRef.equals(primaryParent.getParentRef())) + { + if (log.isDebugEnabled()) + { + log.debug("\tPermission test ignoring secondary parent association to " + + testParentNodeRef); + } + testNodeRef = null; + } + } + else if (log.isDebugEnabled()) + { + log.debug("\tPermission test on non-existing node " + testNodeRef); + } + } + } + else if (cad.parameter.length == 1 && ChildAssociationRef.class.isAssignableFrom(params[cad.parameter[0]])) + { + ChildAssociationRef testParentRef = getArgument(invocation, cad.parameter[0]); + if (testParentRef != null) + { + if (testParentRef.isPrimary()) + { + testNodeRef = testParentRef.getChildRef(); + if (log.isDebugEnabled()) + { + if (nodeService.exists(testNodeRef)) + { + log.debug("\tPermission test on node " + nodeService.getPath(testNodeRef)); + } + else + { + log.debug("\tPermission test on non-existing node " + testNodeRef); + } + } + } + else if (log.isDebugEnabled()) + { + log.debug("\tPermission test ignoring secondary parent association to " + + testParentRef.getParentRef()); + } + } + } + else + { + throw new ACLEntryVoterException("The specified parameter is not a NodeRef or ChildAssociationRef"); + } } else if (cad.typeString.equals(ACL_NODE)) { - if (StoreRef.class.isAssignableFrom(params[cad.parameter])) + if (cad.parameter.length != 1) { - if (invocation.getArguments()[cad.parameter] != null) + throw new ACLEntryVoterException("The specified parameter is not a NodeRef or ChildAssociationRef"); + } + else if (StoreRef.class.isAssignableFrom(params[cad.parameter[0]])) + { + StoreRef storeRef = getArgument(invocation, cad.parameter[0]); + if (storeRef != null) { if (log.isDebugEnabled()) { log.debug("\tPermission test against the store - using permissions on the root node"); } - StoreRef storeRef = (StoreRef) invocation.getArguments()[cad.parameter]; if (nodeService.exists(storeRef)) { testNodeRef = nodeService.getRootNode(storeRef); } } } - else if (NodeRef.class.isAssignableFrom(params[cad.parameter])) + else if (NodeRef.class.isAssignableFrom(params[cad.parameter[0]])) { - testNodeRef = (NodeRef) invocation.getArguments()[cad.parameter]; + testNodeRef = getArgument(invocation, cad.parameter[0]); if (log.isDebugEnabled()) { if (nodeService.exists(testNodeRef)) @@ -322,14 +390,14 @@ public class ACLEntryVoter implements AccessDecisionVoter, InitializingBean { log.debug("\tPermission test on non-existing node " +testNodeRef); } - } } - else if (ChildAssociationRef.class.isAssignableFrom(params[cad.parameter])) + else if (ChildAssociationRef.class.isAssignableFrom(params[cad.parameter[0]])) { - if (invocation.getArguments()[cad.parameter] != null) + ChildAssociationRef testChildRef = getArgument(invocation, cad.parameter[0]); + if (testChildRef != null) { - testNodeRef = ((ChildAssociationRef) invocation.getArguments()[cad.parameter]).getChildRef(); + testNodeRef = testChildRef.getChildRef(); if (log.isDebugEnabled()) { if (nodeService.exists(testNodeRef)) @@ -352,9 +420,13 @@ public class ACLEntryVoter implements AccessDecisionVoter, InitializingBean { // There is no point having parent permissions for store // refs - if (NodeRef.class.isAssignableFrom(params[cad.parameter])) + if (cad.parameter.length != 1) { - NodeRef child = (NodeRef) invocation.getArguments()[cad.parameter]; + throw new ACLEntryVoterException("The specified parameter is not a NodeRef or ChildAssociationRef"); + } + else if (NodeRef.class.isAssignableFrom(params[cad.parameter[0]])) + { + NodeRef child = getArgument(invocation, cad.parameter[0]); if (child != null) { testNodeRef = nodeService.getPrimaryParent(child).getParentRef(); @@ -372,11 +444,12 @@ public class ACLEntryVoter implements AccessDecisionVoter, InitializingBean } } } - else if (ChildAssociationRef.class.isAssignableFrom(params[cad.parameter])) + else if (ChildAssociationRef.class.isAssignableFrom(params[cad.parameter[0]])) { - if (invocation.getArguments()[cad.parameter] != null) + ChildAssociationRef testParentRef = getArgument(invocation, cad.parameter[0]); + if (testParentRef != null) { - testNodeRef = ((ChildAssociationRef) invocation.getArguments()[cad.parameter]).getParentRef(); + testNodeRef = testParentRef.getParentRef(); if (log.isDebugEnabled()) { if (nodeService.exists(testNodeRef)) @@ -456,6 +529,13 @@ public class ACLEntryVoter implements AccessDecisionVoter, InitializingBean } } + @SuppressWarnings("unchecked") + private T getArgument(MethodInvocation invocation, int index) + { + Object[] args = invocation.getArguments(); + return index > args.length ? null : (T)args[index]; + } + private List extractSupportedDefinitions(ConfigAttributeDefinition config) { List definitions = new ArrayList(2); @@ -480,7 +560,7 @@ public class ACLEntryVoter implements AccessDecisionVoter, InitializingBean SimplePermissionReference required; - int parameter; + int[] parameter; String authority; @@ -493,24 +573,36 @@ public class ACLEntryVoter implements AccessDecisionVoter, InitializingBean } typeString = st.nextToken(); - if (!(typeString.equals(ACL_NODE) || typeString.equals(ACL_PARENT) || typeString.equals(ACL_ALLOW) || typeString - .equals(ACL_METHOD) || typeString.equals(ACL_DENY))) + if (!(typeString.equals(ACL_NODE) || typeString.equals(ACL_PRI_CHILD_ASSOC_ON_CHILD) + || typeString.equals(ACL_PARENT) || typeString.equals(ACL_ALLOW) || typeString.equals(ACL_METHOD) || typeString + .equals(ACL_DENY))) { throw new ACLEntryVoterException("Invalid type: must be ACL_NODE, ACL_PARENT or ACL_ALLOW"); } - if (typeString.equals(ACL_NODE) || typeString.equals(ACL_PARENT)) + if (typeString.equals(ACL_NODE) || typeString.equals(ACL_PRI_CHILD_ASSOC_ON_CHILD) + || typeString.equals(ACL_PARENT)) { - if (st.countTokens() != 3) + int count = st.countTokens(); + if (typeString.equals(ACL_PRI_CHILD_ASSOC_ON_CHILD)) { - throw new ACLEntryVoterException("There must be four . separated tokens in each config attribute"); + if (count != 3 && count != 4) + { + throw new ACLEntryVoterException("There must be three or four . separated tokens in each config attribute"); + } + } + else if (count != 3) + { + throw new ACLEntryVoterException("There must be three . separated tokens in each config attribute"); + } + // Handle a variable number of parameters + parameter = new int[count - 2]; + for (int i=0; i(); + Set sourceAspects = nodeDAO.getNodeAspects(nodeId); + for(QName aspectQName : sourceAspects) + { + AspectDefinition aspect = dictionaryService.getAspect(aspectQName); + if(aspect != null) + { + aspects.add(aspectQName); + } + } } nodeMetaData.setAspects(aspects); @@ -593,9 +613,7 @@ public class SOLRTrackingComponentImpl implements SOLRTrackingComponent { paths.add(new Pair(catPair.getFirst().getBaseNamePath(tenantService), catPair.getSecond())); } - - - + nodeMetaData.setPaths(paths); } @@ -894,4 +912,23 @@ public class SOLRTrackingComponentImpl implements SOLRTrackingComponent nodeDAO.setCheckNodeConsistency(); return nodeDAO.getMaxTxnIdByCommitTime(maxCommitTime); } + + /* (non-Javadoc) + * @see org.alfresco.repo.solr.SOLRTrackingComponent#getMaxChangeSetCommitTime() + */ + @Override + public Long getMaxChangeSetCommitTime() + { + return aclDAO.getMaxChangeSetCommitTime(); + } + + /* (non-Javadoc) + * @see org.alfresco.repo.solr.SOLRTrackingComponent#getMaxChangeSetId() + */ + @Override + public Long getMaxChangeSetId() + { + long maxCommitTime = System.currentTimeMillis()+1L; + return aclDAO.getMaxChangeSetIdByCommitTime(maxCommitTime); + } } diff --git a/source/java/org/alfresco/repo/thumbnail/FailedThumbnailSourceAspect.java b/source/java/org/alfresco/repo/thumbnail/FailedThumbnailSourceAspect.java index 4cc4b8df50..daab686b60 100644 --- a/source/java/org/alfresco/repo/thumbnail/FailedThumbnailSourceAspect.java +++ b/source/java/org/alfresco/repo/thumbnail/FailedThumbnailSourceAspect.java @@ -30,6 +30,8 @@ import org.alfresco.repo.policy.Behaviour; import org.alfresco.repo.policy.BehaviourFilter; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.service.cmr.lock.LockService; import org.alfresco.service.cmr.lock.LockStatus; import org.alfresco.service.cmr.repository.ChildAssociationRef; @@ -136,12 +138,20 @@ public class FailedThumbnailSourceAspect implements NodeServicePolicies.OnDelete } @Override - public void onContentUpdate(NodeRef nodeRef, boolean newContent) + public void onContentUpdate(final NodeRef nodeRef, boolean newContent) { - if (nodeService.exists(nodeRef) && lockService.getLockStatus(nodeRef) != LockStatus.LOCKED) + AuthenticationUtil.runAsSystem(new RunAsWork() { - deleteFailedThumbnailChildren(nodeRef); - } + @Override + public Object doWork() + { + if (nodeService.exists(nodeRef) && lockService.getLockStatus(nodeRef) != LockStatus.LOCKED) + { + deleteFailedThumbnailChildren(nodeRef); + } + return null; + } + }); } /** diff --git a/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java b/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java index a964ae1a92..1b0ad92cc2 100644 --- a/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java +++ b/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java @@ -73,6 +73,8 @@ import org.alfresco.service.namespace.QName; import org.alfresco.util.GUID; import org.alfresco.util.collections.CollectionUtils; import org.alfresco.util.collections.Function; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.hibernate.CacheMode; import org.hibernate.Criteria; import org.hibernate.FlushMode; @@ -125,6 +127,8 @@ import org.springmodules.workflow.jbpm31.JbpmTemplate; */ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine { + private static Log logger = LogFactory.getLog(JBPMEngine.class); + // Implementation dependencies protected NodeService nodeService; protected ServiceRegistry serviceRegistry; @@ -442,7 +446,15 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine { public WorkflowDefinition apply(ProcessDefinition value) { - return createWorkflowDefinition(value); + try + { + return createWorkflowDefinition(value); + } + catch (Exception ex) + { + logger.warn("Unable to load workflow definition: '" + value + "' due to exception.", ex); + return null; + } } }); } @@ -453,7 +465,15 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine { public WorkflowInstance apply(ProcessInstance value) { - return createWorkflowInstance(value); + try + { + return createWorkflowInstance(value); + } + catch (Exception ex) + { + logger.warn("Unable to load workflow instance: '" + value + "' due to exception.", ex); + return null; + } } }); } @@ -893,7 +913,15 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine // retrieve workflow GraphSession graphSession = context.getGraphSession(); ProcessInstance processInstance = getProcessInstanceIfExists(graphSession, workflowId); - return createWorkflowInstance(processInstance); + try + { + return createWorkflowInstance(processInstance); + } + catch (Exception ex) + { + logger.warn("Unable to load workflow instance: '" + processInstance + "' due to exception.", ex); + return null; + } } }); } @@ -1104,7 +1132,14 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine ProcessInstance processInstance = processInstances.get(workflowId); // TODO: Determine if this is the most appropriate way to cancel workflow... // It might be useful to record point at which it was cancelled etc - workflowInstances.add(createWorkflowInstance(processInstance)); + try + { + workflowInstances.add(createWorkflowInstance(processInstance)); + } + catch(Exception ex) + { + logger.warn("Unable to load workflow instance: '" + processInstance + "' due to exception.", ex); + } // delete the process instance graphSession.deleteProcessInstance(processInstance, true, true); @@ -1514,12 +1549,20 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine /// ------------------------ for(Object[] row : rows) { - WorkflowTask workflowTask = makeWorkflowTask(row, taskInstanceCache, variablesCache); - if(workflowTask !=null ) + try { - workflowTasks.add(workflowTask); + WorkflowTask workflowTask = makeWorkflowTask(row, taskInstanceCache, variablesCache); + if(workflowTask != null) + { + workflowTasks.add(workflowTask); + } } - } + catch (Exception ex) + { + logger.warn("Unable to load workflow instance: '" + row[0] + "' due to exception.", ex); + continue; + } + } return workflowTasks; } @@ -1774,8 +1817,16 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine workflowTasks = new ArrayList(filteredTasks.size()); for (TaskInstance task : filteredTasks) { - WorkflowTask workflowTask = createWorkflowTask(task); - workflowTasks.add(workflowTask); + try + { + WorkflowTask workflowTask = createWorkflowTask(task); + workflowTasks.add(workflowTask); + } + catch (Exception ex) + { + logger.warn("Unable to load workflow task: '" + task + "' due to exception.", ex); + continue; + } } } diff --git a/source/java/org/alfresco/repo/workflow/jscript/JscriptWorkflowInstance.java b/source/java/org/alfresco/repo/workflow/jscript/JscriptWorkflowInstance.java index e95a9d70f4..574cfa36e4 100644 --- a/source/java/org/alfresco/repo/workflow/jscript/JscriptWorkflowInstance.java +++ b/source/java/org/alfresco/repo/workflow/jscript/JscriptWorkflowInstance.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -185,9 +185,19 @@ public class JscriptWorkflowInstance implements Serializable /** * Delete workflow instance + * @deprecated as 'delete' is a JavaScript reserved word and so is unusable. Use {@link #remove()} instead. */ - public void delete() - { - serviceRegistry.getWorkflowService().deleteWorkflow(this.id); - } + public void delete() + { + this.remove(); + } + + /** + * Deletes workflow instance. + * @since 3.4.9 + */ + public void remove() + { + serviceRegistry.getWorkflowService().deleteWorkflow(this.id); + } } diff --git a/source/java/org/alfresco/service/cmr/security/PersonService.java b/source/java/org/alfresco/service/cmr/security/PersonService.java index 324b28dd57..b332c2da37 100644 --- a/source/java/org/alfresco/service/cmr/security/PersonService.java +++ b/source/java/org/alfresco/service/cmr/security/PersonService.java @@ -333,4 +333,13 @@ public interface PersonService */ @NotAuditable public int countPeople(); + + /** + * Is the specified user, enabled + * @throws NoSuchPersonException + * if the user doesn't exist + * @return true = enabled. + */ + @NotAuditable + public boolean isEnabled(final String userName); } diff --git a/source/java/org/alfresco/util/DynamicallySizedThreadPoolExecutor.java b/source/java/org/alfresco/util/DynamicallySizedThreadPoolExecutor.java deleted file mode 100644 index 91a5a513c6..0000000000 --- a/source/java/org/alfresco/util/DynamicallySizedThreadPoolExecutor.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * 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 . - */ -package org.alfresco.util; - -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.RejectedExecutionHandler; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.ReentrantLock; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * This is an instance of {@link java.util.concurrent.ThreadPoolExecutor} which - * behaves how one would expect it to, even when faced with an unlimited - * queue. Unlike the default {@link java.util.concurrent.ThreadPoolExecutor}, it - * will add new Threads up to {@link #setMaximumPoolSize(int) maximumPoolSize} - * when there is lots of pending work, rather than only when the queue is full - * (which it often never will be, especially for unlimited queues) - * - * @author Nick Burch - */ -public class DynamicallySizedThreadPoolExecutor extends ThreadPoolExecutor -{ - private static Log logger = LogFactory.getLog(DynamicallySizedThreadPoolExecutor.class); - - private final ReentrantLock lock = new ReentrantLock(); - private int realCorePoolSize; - - public DynamicallySizedThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, - BlockingQueue workQueue, RejectedExecutionHandler handler) - { - super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler); - this.realCorePoolSize = corePoolSize; - } - - public DynamicallySizedThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, - BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) - { - super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler); - this.realCorePoolSize = corePoolSize; - } - - public DynamicallySizedThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, - BlockingQueue workQueue, ThreadFactory threadFactory) - { - super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory); - this.realCorePoolSize = corePoolSize; - } - - public DynamicallySizedThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, - BlockingQueue workQueue) - { - super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue); - this.realCorePoolSize = corePoolSize; - } - - @Override - public void setCorePoolSize(int corePoolSize) - { - this.realCorePoolSize = corePoolSize; - super.setCorePoolSize(corePoolSize); - } - - @Override - public void execute(Runnable command) - { - // Do we want to add another thread? - int threadCount = getPoolSize(); - if(logger.isDebugEnabled()) - { - logger.debug("Current pool size is " + threadCount + ", real core=" + realCorePoolSize + - ", current core=" + getCorePoolSize() + ", max=" + getMaximumPoolSize()); - } - - if(threadCount < getMaximumPoolSize()) - { - // We're not yet at the full thread count - - // Does the queue size warrant adding one? - // (If there are more than the maximum pool size of jobs pending, - // it's time to add another thread) - int queueSize = getQueue().size() + 1;// New job not yet added - if(queueSize >= getMaximumPoolSize()) - { - lock.lock(); - int currentCoreSize = getCorePoolSize(); - if(currentCoreSize < getMaximumPoolSize()) - { - super.setCorePoolSize(currentCoreSize+1); - - if(logger.isInfoEnabled()) - { - logger.info("Increased pool size to " + getCorePoolSize() + " from " + - currentCoreSize + " due to queue size of " + queueSize); - } - } - lock.unlock(); - } - } - - // Now run the actual work - super.execute(command); - } - - @Override - protected void afterExecute(Runnable r, Throwable t) - { - // If the queue is looking empty, allow the pool to - // get rid of idle threads when it wants to - int threadCount = getPoolSize(); - if(threadCount == getMaximumPoolSize() && threadCount > realCorePoolSize) - { - int queueSize = getQueue().size(); - int currentCoreSize = getCorePoolSize(); - if(queueSize < 2 && currentCoreSize > realCorePoolSize) - { - // Almost out of work, allow the pool to reduce threads when - // required. Double checks the sizing inside a lock to avoid - // race conditions taking us below the real core size. - lock.lock(); - currentCoreSize = getCorePoolSize(); - if(currentCoreSize > realCorePoolSize) - { - super.setCorePoolSize(currentCoreSize-1); - - if(logger.isInfoEnabled()) - { - logger.info("Decreased pool size to " + getCorePoolSize() + " from " + - currentCoreSize + " (real core size is " + realCorePoolSize + - ") due to queue size of " + queueSize); - } - } - lock.unlock(); - } - } - } -} diff --git a/source/java/org/alfresco/util/TraceableThreadFactory.java b/source/java/org/alfresco/util/TraceableThreadFactory.java deleted file mode 100644 index f0fde3372b..0000000000 --- a/source/java/org/alfresco/util/TraceableThreadFactory.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * 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 . - */ -package org.alfresco.util; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * A thread factory that spawns threads that are statically visible. Each factory uses a unique - * thread group. All the groups that have been used can be fetched using - * {@link #getActiveThreadGroups()}, allowing iteration of the the threads in the group. - * - * @since 2.1 - * @author Derek Hulley - */ -public class TraceableThreadFactory implements ThreadFactory -{ - private static final AtomicInteger factoryNumber = new AtomicInteger(1); - private static List activeThreadGroups = Collections.synchronizedList(new ArrayList(1)); - - /** - * Get a list of thread groups registered by the factory. - * - * @return Returns a snapshot of thread groups - */ - public static List getActiveThreadGroups() - { - return activeThreadGroups; - } - - private final ThreadGroup group; - private String namePrefix; - private final AtomicInteger threadNumber; - private boolean threadDaemon; - private int threadPriority; - - - public TraceableThreadFactory() - { - this.group = new ThreadGroup("TraceableThreadGroup-" + factoryNumber.getAndIncrement()); - TraceableThreadFactory.activeThreadGroups.add(this.group); - - this.namePrefix = "TraceableThread-" + factoryNumber.getAndIncrement() + "-thread-"; - this.threadNumber = new AtomicInteger(1); - - this.threadDaemon = true; - this.threadPriority = Thread.NORM_PRIORITY; - } - - /** - * @param daemon true if all threads created must be daemon threads - */ - public void setThreadDaemon(boolean daemon) - { - this.threadDaemon = daemon; - } - - /** - * - * @param threadPriority the threads priority from 1 (lowest) to 10 (highest) - */ - public void setThreadPriority(int threadPriority) - { - this.threadPriority = threadPriority; - } - - public Thread newThread(Runnable r) - { - Thread thread = new Thread( - group, - r, - namePrefix + threadNumber.getAndIncrement(), - 0); - thread.setDaemon(threadDaemon); - thread.setPriority(threadPriority); - - return thread; - } - - public void setNamePrefix(String namePrefix) - { - this.namePrefix = namePrefix; - } - - public String getNamePrefix() - { - return this.namePrefix; - } - -}