mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-06-30 18:15:39 +00:00
34474: ALF-13169 Tomcat fails to shutdown - fix non daemon Timer's 34475: Part 1: Fix for ALF-13244 SOLR Multi-threaded tracking is required for performance - simultaneous document transformations - multi-threaded transaction and node tracking (off by default at the moment) - fix index/repo sync check failure if ACLs have been indexed but no transactions - minimise data sent back from query responses (not all stored fields) - added SOLR side config for HTTPClient pooling, cache sizing and tracker configuration - fixed SOLR incremental cache update for merges that end with all deletions in the old index - fixed unclosed stream in SolrKeyResourceLoader 34478: ALF-13050 - CIFS: Disabling account is not respected Also contains major rework of logging and exception handling. 34499: Fix for ALF-13150 34526: Fix for ALF-13288 34530: Minor CSS tweaks after changes for ALF-11991 34539: ALF-13176 - Implement Word for Mac 2011 Cifs Shuffle. 34541: ALF-13244 SOLR Multi-threaded tracking is required for performance - simultaneous document transformations - multi-threaded ACL tracking - multi-threaded statistics and reporting control - nodes that can not be indexed have an error record added to the index and do not block indexing the transaction (nodes unindexed due to exceptions can be found by ID query and the exception stored in the index) - nodes that are not-indexed have a minimal record added to the index for index consistency checking (unindexed nodes can be found by ID query) 34544: Add support for CIFS Level II shared oplocks. ALF-13138, ALF-13110. Fixed CIFS open for attributes only access preventing oplock on the following file open. Fixed reporting serialized copies of file access tokens as leaked. 34576: ALF-12767 - CIFS TextEdit - File has been modified outside TextEdit 34577: incorrectly checked in copy of network-protocol-context.xml 34580: ALF-13215: Ensure that permissions for everyone cannot be upgraded on moderated or private site. Fixed inconsistency between permissions shown in properties and in dialog 34582: ALF-13332: Updated modifier link for correct profile 34609: ALF-12740: Update to previous fix (only apply to IE8 and below) 34623: ALF-12767 - CIFS TextEdit - File has been modified outside TextEdit 34636: Fix for ALF-13365 SOLR: Recently modified docs dashlet sorts incorrectly - respect short property names on sort requests @cm:created and not require the full @{uri...}created 34659: ALF-2550 - added enterprise repo config files. 34715: Fix for __ShowDetails desktop action returned URL is truncated if hostname too long. ALF-13202. 34726: ALF-13293: Webdav: Version history lost after editing content in Finder 34738: ALF-7883: WebDAV: support HEAD method for folder - Fix by Pavel 34743: Fix for ALF-13244 SOLR Multi-threaded tracking is required for performance - simultaneous document transformations - batch fetch for nodes in transaction, acls in sets, and acls and readers - config for batch fetching - Better reporting for ACL set indexing 34747: ALF-13262: adding missing indexes for new schema's (activiti-schema create) + schema patch for existing schema 34817: Merged V4.0 to V4.0-BUG-FIX 34493: SPANISH: translation updates based on EN r34103 34498: Fixed ALF-12031: WCM: Content cannot be expired: avmExpiredContentTrigger is missing - Side-effect of ALF-11644: AVM cleanup jobs run when WCM is not installed - WORKAROUND: Get file 'root\projects\installer\wcm-bootstrap-context.xml' and use that 34525: Fix for ALF-13210: - removed "unsupported" from bulk filesystem import web pages 34531: Fix for ALF-13117 and ALF-13273 34549: Merged BRANCHES/DEV/BELARUS/HEAD-2012_03_15 to BRANCHES/V4.0: 34528: ALF-12874: 34552: ALF-13322: Fixed doc lib reload loop caused by "#" in folder name 34553: ALF-13311: Ensure images can be linked in TinyMCE create HTML content editor 34556: Minor: removed unused code 34557: Merged DEV to V4.0 34537: ALF-13035: Add "START WITH" parameter to IDENTITY field. ALF-13034: Add "optional" parameter for statement that drops index that was generated automatically. 34567: ALF-11047: Ensure that Explorer linked files and folders (from outside of sites) display correctly 34578: Fixes: ALF-11744: Dates rendered with the form service date control are rendered on the server, so show server time. - I've added the timezone to the display format and the ISO8601 date to the as an attribute on the HTML element to allow client side parsing - Adds client side parsing on the Doc Details page, so times are shown in the timezone of the user's browser. 34583: GERMAN: Translation update, based on EN r34103, Fixes: ALF-13075, 34584: FRENCH: Translation update based on EN r34103, Fixes: ALF-13002, ALF-13003, ALF-13020 34585: ITALIAN: Translation update based on EN r34103 34586: JAPANESE: Translation update based on EN r34103 34587: DUTCH: Translation update based on EN r34103, Fixes: ALF-12575. 34626: Fixes: ALF-13375 - Date rendering bug in search results 34630: Further fix for ALF-13375 that modifies Alfresco.util.formatDate's ISO8601 support for backward compatibility (e.g. passing in non ISO strings). 34635: ALF-12061: Mac support: Document Connection always throws an error - Case sensitivity fix by Pavel 34653: ALF-12308, ALF-12309, ALF-12554: Stack specific script errors 34655: Fix for ALF-12723 CMIS: Over-riding cm:autoVersionOnUpdateProps in custom model prevents startup 34656: Merged HEAD to BRANCHES/V4.0: 34654: Fixes: ALF-13389: Old element id used when setting event end date. 34657: Translation updates for all languages except JA. 34660: Fix to license driven config files to remove erroneous characters 34669: Merged DEV to V4.0 34663: ALF-12242: User activation issue InviteHelper.acceptNominatedInvitation() method was changed to enable user account in any case(no matter was it enabled/disabled before) 34681: Merged DEV/THEMIS2 to V4.0 34472: Document List Customization Refactor - SLingshotSiteModuleEvalutaor now has new <applyForNonSite> param that defaults to false for backward compability - Slingshot extension points, surf-doclist.get now uses 2 spring beans: * "resolver.doclib.doclistDataUrl" to get the repo doclist data url * "resolver.doclib.actionGroup" to get each item/nodes action group id 34692: Fix for ALF-12715 - Incorrect SPP working (mimetype not set on document stored via ADM Remote Store API) 34708: ALF-13239: Merged V3.4-BUG-FIX (3.4.9) to V4.0 (4.0.1) 34707: ALF-13239 Share rule to convert to PNG fails on JPG images - Issue was showing up in 4.0.1 as a change was made for iPad that introduced an imageOptions.isAutoOrient() setting. This forced a concatenation of null with " -auto-orient". However there are also crop and resize options that could also do this even in 3.4 Setting the commandOptions String to "" when null, is fine as this is how property value nulls are handled later anyway. 34718: JAPANESE: Localisation of Company specific contact information & addition of timezone to form control. 34719: FRENCH: File consistency tweak. 34746: ALF-12903: Create HTML content fix 34754: Merged PATCHES/V4.0.0 to V4.0 34750: Reinstate ${version.label} into version.number property 34810: Merged DEV to V4.0 (with corrections) 34807: ALF-13290 : Mac Support: Error appears after collaborator saves changes to the document deleteFailedThumbnailChildren method should be run as system user as it may fails with AccesssDenied if collaborator updates document 34876: Fix fo ALF-13503 Add SOLR client API tests to the SystemBuildTest project - SOLR API tests run embedded with SSL 34984: ALF-13109 - Correction to NTIOCtl.FsCtlCreateOrGetObjectId 35009: Merged BRANCHES/DEV/V3.4-BUG-FIX to BRANCHES/DEV/V4.0-BUG-FIX: 35008: Fix for ALF-12817. Fixed as suggested - new method remove(). 35031: Fix for ALF-12309 35032: Fix fo ALF-13535 using CMIS, on-disk tickets cache can grow unbounded - expire tickets based on inactivity by default - added job to clean up expired tickets - all are configurable 35033: Fix fo ALF-13535 using CMIS, on-disk tickets cache can grow unbounded - avoid NPE for null tickets 35037: Fix for ALF-13505 SOLR tracking readers does not encode all uids correctly - fixed reader encoding 35049: ALF-13384 - Saving large Word (mac 2011) document via CIFS fails in Mac OS X Lion 35053: Merged V4.0 (V4.0.1) to V4.0-BUG-FIX (4.0.2) 34844: Merged V3.4-BUG-FIX (3.4.9) to V4.0 (4.0.1) 34843: ALF-5830 show_audit.ftl template doesn't work anymore - Removed L10n messages that are no longer used (should have been removed in 3.4.6 when this issue was fixed) 34847: Merged HEAD to BRANCHES/V4.0: 34804: Fixes: ALF-13309: Issue with over zealous HTML escaping with truncated descriptions in the Calendar Agenda view. 34861: ALF-13497: Merged PATCHES/V4.0.0 to V4.0 34813: ALF-13115: No feedback is given to the user when Approve/Reject is clicked for a task when they followed a link to the task in an email. - Fix by Pavel, reviewed by Kev - Now they get a confirmation message followed by a redirect to their dashboard 34862: Fix for ALF-10823 "allowGuestLogin=false" and Share then fills the alfresco error log with "Guest authentication not supported" Fix for ALF-12678 Errors in log on startup (ts.alfresco.com 4.0) - improved handling of 500 errors relating to GuestAuthNotSupported when alfresco.authentication.allowGuestLogin=false 34867: Merged DEV to V4.0 34565: ALF-13074: JBPM workflow definitions are not resilient to missing model definitions WARN messages have been added if JBPM workflow definitions cannot be loaded in the model definitions. 34855: ALF-13074: JBPM workflow definitions are not resilient to missing model definitions Reimplemented to handle all exceptions during constructing WorkflowInstances WorkflowTasks and WorkflowDefinitions. 34859: ALF-13074: JBPM workflow definitions are not resilient to missing model definitions Logger messages was changed to correspond the logger pattern. 34893: Translation updates for DE and ES. 34894: Fixes: ALF-13518; Updates Calendar event object's URL to work out of context. 34896: FRENCH: Translates new strings. 34915: Merged DEV to V4.0 34912: ALF-13267: There should not be a web-client-config-custom.xml in alfresco.war Move "modules\quickr\config\alfresco\extension\web-client-config-custom.xml" to "modules\quickr\config\alfresco\module\org.alfresco.module.quickr\ui\web-client-custom.xml". 34913: ALF-13267: There should not be a web-client-config-custom.xml in alfresco.war Delete "modules\quickr\config\alfresco\extension\web-client-config-custom.xml". 34916: ALF-13267: Merged V3.4 to V4.0 (and reversed previous duplicate fix) 24828: Merged BRANCHES/DEV/BELARUS/V3.4-2011_01_13 to BRANCHES/V3.4: 24824: ALF-6361: web-client-config-custom.xml doesn't work in /alfresco/tomcat/shared/classes/alfresco/extension 34929: ALF-12242: Issues activating users when more than one member in the authentication chain - Correction to fix that caused regressions ALF-13494, ALF-13498 - Need to check for the mutability of a user's authentication before trying to enable it - Also chaining of the authentication enabled attribute should assume true until false found, not the other way around 34930: ALF-12242: Reverted change to this class as it wasn't necessary and wouldn't work! 34932: ALF-13453: Enable XMLConstants.FEATURE_SECURE_PROCESSING feature on Transformer Factory to prevent remote code execution - Now SecureTransformerFactory should be used as a standard 34965: Merged PATCHES/V4.0.0 to V4.0 34959: ALF-13550: Fix for ALF-13546 SOLR tracking fails for nodes with content and no auditable aspect - NPE as there is no last modification date to use 34960: ALF-13551: Merged BRANCHES/DEV/V4.0-BUG-FIX to PATCHES\V4.0.0 - fix for ALF-13544 When SOLR encounters an error indexing a document, subsequent indexing does not occur 34541: ALF-13244 SOLR Multi-threaded tracking is required for performance - simultaneous document transformations - nodes that can not be indexed have an error record added to the index and do not block indexing the transaction (nodes unindexed due to exceptions can be found by ID query and the exception stored in the index) - nodes that are not-indexed have a minimal record added to the index for index consistency checking (unindexed nodes can be found by ID query) 34968: ALF-13453: Reversed XSLTProcessor and XSLTRenderingEngine changes for now as they break http://wiki.alfresco.com/wiki/WCM_Forms_Rendering and model handling via bsf extensions. A more sophisticated approach is required. See bug for more info. 34972: ALF-13340: Upgrade postgres JDBC driver to tested/supported version! 34997: ALF-13453, ALF-13565: Fully reverted revision 34932 as it prevents startup on Weblogic 34998: Merged V4.0-BUG-FIX to V4.0 34992: DUTCH: translation updates based on EN r34861 34993: FRENCH: Translation updates based on r34861 34994: ITALIAN: Translation updates based on r34861 35013: ALF-13561: Not found error after uploading new version - Fix by Pavel 35034: Fixes ALF-13570: Error loading event info panel. 35039: ALF-13573: Merged V3.4-BUG-FIX (3.4.9) to V4.0 (4.0.1) 35022: ALF-13451: Allow modules to configure mimetypes 35041: ALF-13466: Error is displayed by approve or reject wcm workflow - Fixed regression caused by ALF-4098 - Protected calls to new addNewChildrenIfAny() method with isDirectory() checks 35042: GERMAN: Translation updates based on r35029, and fixes ALF-12471. 35043: SPANISH: Translation updates based on r35029, and fixes ALF-12471. 35044: FRENCH: Translation updates based on r35029, and fixes ALF-12471. 35045: ITALIAN: Translation updates based on r35029, and fixes ALF-12471. 35046: JAPANESE: Translation updates based on r35029, and fixes ALF-12471. 35047: DUTCH: Translation updates based on r35029, and fixes ALF-12471. 35090: Remove Kofax. It has been migrated to integrations/kofax 35097: Added new file server cluster tests. Open for attributes only overlapped with open with oplock. Open with oplock with break to level II shared oplock. 35099: JLAN Client updates to support level II oplocks, required by new cluster tests. 35100: Various oplock related fixes, including problems opening file on second cluster node. ALF-13109. 35107: remove errant '>' 35116: ALF-13401 - Mac LION Powerpoint CIFS 35162: Removed spurious attempt to force a concurrency exception for getNodePair after a node had actually been deleted. Code would retry 50 times before failing. Reviewed with Derek, its not the node service's job to second guess that there may be a concurrency problem in a client's cache. 35164: Fix for ALF-13641 - Negative cases for date value in propertyNegative cases for date value in property. Today button 35169: ALF-13401, ALF-12393: Added exception translation to AbstractReindexComponent retrying transactions, following change in r35162 35172: ALF-13626: category.put.json.ftl has wrong bracket 35173: ALF-12749 - CIFS: Editing of ppt/pptx files fails (MacOSx specific) 35174: Fix for ALF-13556 - Sorting for custom model fields doesn't work for search results in Share 35176: Fix for ALF-4281 - Script error at 'Email space users' form 35186: Merged BRANCHES/DEV/DAM/V4.0-BUG-FIX-34847 to BRANCHES/DEV/V4.0-BUG-FIX: 34875: Creating new branch from $FROM 34939: Merged BRANCHES/DEV/DAM/V4.0-BUG-FIX-34397 to BRANCHES/DEV/DAM/V4.0-BUG-FIX-34847: 34400: Creating new branch from $FROM 34422: Merged DEV/DAM-0.1 to DEV/DAM/V4.0-BUG-FIX-34397 34085: Allow for generateThumbnailUrl to accept a rendition name parameter. 34086: Changed simpleView view type switch to integer implementation rather than boolean. 34087: Pulled specific rendering code for simple and detail view into separate view renderer objects. 34092: If simpleView was stored as a boolean convert it to an integer for ALF-12952. 34423: Merged DEV/DAM/HEAD-34276 to DEV/DAM/V4.0-BUG-FIX-34397 34307: ALF-12952: Change DocumentList simpleView Nav Switch to an Int Implementation 34957: ALF-12952: Change DocumentList simpleView Nav Switch to an Int Implementation - Removed ability to specify index on registerViewRenderer - Added firing of setupAdditionalViewRenderers to make it easier for extensions to register themselves at the appropriate time 35021: ALF-12955: Share Document Library and Repository Browser Should Easily Allow for Additional Views - Changed viewRenderers to an object implementation with storage/retrieval via named properties or 'keys' 35050: ALF-12955: Share Document Library and Repository Browser Should Easily Allow for Additional Views - Renamed simpleView preference and option to viewRendererName - Reintroduced simpleView boolean preference and option as deprecated to allow deletion of old preference - Renamed viewRendererOrder to viewRendererNames - Added default viewRendererNames at DocumentList.options level - Renamed widgets.simpleDetailed to widgets.viewRendererSelect but did NOT change HTML id for backwards compatibility - Renamed onSimpleDetailed to onViewRendererSelect - Added deletion of deprecated simpleView preference if it exists 35056: ALF-12955: Share Document Library and Repository Browser Should Easily Allow for Additional Views - Made viewRenderer methods a proper Alfresco.ViewRenderer object which is more easily extended - Added name property to ViewRenderer constructor and changed registerViewRenderer to use that as a key - With more strictly defined ViewRenderers in place, changed select button to iterate over viewRendererNames rather than explicit list 35104: ALF-12955: Share Document Library and Repository Browser Should Easily Allow for Additional Views - Added markup tag around the document list container 35126: ALF-12955: Share Document Library and Repository Browser Should Easily Allow for Additional Views - Added markup tag documentListConstructorSetOptions around setOptions after DocumentList object constructor - Added markup tag documentListViewRendererSelect around view select buttons - Added markup tag documentListShowFolders around show folders button - Added markup tag documentListSortSelect around sort selection buttons - Renamed Alfresco.ViewRenderer to more specific Alfresco.DocumentListViewRenderer and private methods similarly - Added default for viewRendererName if it's undefined in options - Added check for availability of renderer specified in user preference, if not use default, and consolidated renderer index lookup 35179: ALF-12955: Share Document Library and Repository Browser Should Easily Allow for Additional Views - Removed documentListConstructorSetOptions 35194: Temp disable cifs text edit test. 35197: ALF-13097 - IMAP templates have wrong mimetype 35201: Merged V3.4-BUG-FIX to V4.0-BUG-FIX 34462: Merged DEV to V3.4-BUG-FIX 34461: ALF-10759: Advanced search fails for sub-element tags UITagSelector component which allows Advanced Search to add new tag option to search 34479: Merged V3.4 to V3.4-BUG-FIX (RECORD ONLY) 34477: ALF-13237: Yet another 13th hour Spring Surf Regression - Can't afford to pull in all the latest surf goodies so overriding PageImpl.class with one corresponding to Surf revision 1034 in WEB-INF/classes, just for 3.4.8 34515: ALF-9855: Alfresco side to support standard Adobe-Japan1 PDF fonts in swftools - Bitrock binaries provided 34518: ALF-13266: Ubuntu installation fails in non-obvious way when machine lacks sufficient memory - Fix from Bitrock - L10N required 34536: Merged DEV to V3.4-BUG-FIX 34529: ALF-13135: Impossible to Add new member on Workspace using email address NPE fix if AD users don't have e-mail address as a property. 34538: ALF-12812 Saving files with apps on Mac OS X Lion in CIFS doesn't invoke rules (Update rule fires BEFORE, FileFolderInterceptor recalcs HIDDEN and TEMPORARY ) 34542: Add support for Level II shared oplock. ALF-13093, ALF-12328. Fixed CIFS open for attributes only access preventing oplock on the following file open. 34543: Oplock and open for attributes fixes to the repo/AVM filesystems. ALF-13093, ALF-12328. 34579: ALF-13284: Removing obselete files 34603: ALF-10833 Alfresco does not show correct thumbnails for some specific kind of PDFs - Patched PDFRenderer-0.9.1 to return a null page if there was an error. The code structure did not lend itself to simply throwing the exception. - Modified PdfToImageContentTransformer to check for a null page and it then throws an AlfescoRuntimeException which causes the failover transformer to use the next transformer in the list: PDBBox which is able to transform the pdf and the image that was missing. 34617: Add missing source Java folder. 34629: ALF-13188: Content IO Channel not closed 34697: ALF-13149: Start up performance suffers if the alf_transaction table grows too large. 34712: ALF-13063: sample settings for DB2 34803: New installer translations from Gloria 34809: ALF-11956: Merged BELARUS/V3.4-BUG-FIX-2012_01_26 to V3.4-BUG-FIX (V3.4.9) << In addition to the 2 merged revisions, includes the change for ALF-11972 and test all-widgets.xsd >> 33715: ALF-11956: WCM accessibility - sandbox name oriented titles were added almost to all action links at 'Browse Website' page view; - adding titles to image tags functionality was added to ActionLinkRenderer, UIMenu and UISandboxes (this includes arrow icons for 'Web Forms' and 'Modified Items'); - titles were added to XForm Date/Time picker controls (text input and arrow buttons); - 'Click to edit' functionality via keyboard availability was added to XForms TinyMCE editor control (using 'Tab' key, 'Alt' + 'E' in IE or 'Alt' + 'Shift' + 'E' in FireFox); - additional i18n properties for Date/Time picker and action link titles were added 34625: ALF-11956: WCM accessibility Increasing XForms widgets readability by screen reader tools: - Tiny MCE 3.2.7 buttons; - required fields; - inputs labels; - VGroup, HGroup and Repeating widgets folding icons/buttons and others ALF-11972: Title attributes for the WCM form element xs:anyURI not included to allow multiple xs:anyURI file picker "Select" buttons to be distinguished by screen readers - Change defined in JIRA 34846: Translation updates: - FR: Missing Strings - DE: Fixes encoding issue 34881: ALF-13512: Merged PATCHES/V3.4.8 to V3.4-BUG-FIX 34829: ALF-12621: Sort order of folders including hyphens ( - ) are different in folder-tree and view on folders (in Share) - Switched from using JS sort to Java locale-based sort 34845: ALF-12621: Fixed array typing problems in previous checkin 34918: Fix for ALF-13385 Access DENIED api does not seem to work - changed default behaviour to any-deny-denies - config to switch back - needs custom port to 4.0 for SOLR - unit tests added 34919: Fix for ALF-13385 Access DENIED api does not seem to work - added property based configuration and default configuration check 34937: ALF-11956: Merged BELARUS/V3.4-BUG-FIX-2012_01_26 to V3.4-BUG-FIX (V3.4.9) 34886: ALF-11956: WCM accessibility - headings functionality is added. WAI-ARIA markup was used; - alert for XForms validation errors is added. WAI-ARIA markup was used; - previous accessibility changes tested and fixed against the new functionality 35003: Merged HEAD to V3.4-BUG-FIX 34673: Changed from time-based module and component names to GUID-based names. Not likely to affect anything. 35057: Fix for ALF-12590 Share - Document library doesn't return subfolders when parent space contains the character "- " - updated to the latest version of jaxen (which now includes saxpath) - the problem path is now parsed correctly 35074: ALF-13597: Merged PATCHES/V3.4.6 to V3.4-BUG-FIX 34978: ALF-13489: Index tracker now has ability to distinguish create/update/rename/link/unlink - Will prevent unnecessary cascading PATH regeneration on remote cluster nodes - QNames and noderefs of parents in index compared with those in the database - Experimental - needs testing 34983: ALF-13489: Correction to renamed node detection 34985: ALF-13489: Even more foolproof parent assoc cross-referencing - Should handle duplicate QNames, etc. - Renames now just an add and a remove 35075: ALF-13598: Merged PATCHES/V3.4.6 to V3.4-BUG-FIX 34872: Merged DEV (by Pavel) to PATCHES/V3.4.6 (and refactored) 34554: ALF-11777 : Persistent lock is left on document in certain use cases when editing online (spp) 1. From now documents are locked for maximum 24 hours when working through WebDAV/Vti. 2. Session listeners were added for web-client and vti-module to allow handling session expiration event. 3. WebDAVLockService class was implemented. It is used by session listeners to perform session cleaning (forcibly unlock all documents that were persistently locked during http session). 4. LOCK/UNLOCK webdav methods and Get/Checkout/UncheckoutDocumentMethod vti methods where updated to correctly populate session list of locked documents. 34832: ALF-11777 : Persistent lock is left on document in certain use cases when editing online (spp) 1. From now documents are locked for maximum 24 hours when working through WebDAV/Vti. 2. Session listener was added for webdav/vti to allow handling session expiration event. 3. LOCK/UNLOCK webdav methods and Get/Checkout/UncheckoutDocumentMethod vti methods where updated to use shared code to lock/unlock nodes. 34833: ALF-11777 : Persistent lock is left on document in certain use cases when editing online (spp) 1. Remove unnecessary classes after 34554 rev. 34852: ALF-11777 : Persistent lock is left on document in certain use cases when editing online (spp) 1. Some changes after David's review of revisions 34832, 34833. 34874: ALF-11777: Fixed typo 35078: ALF-12785: BaseDownloadContentServlet could co into an infinite loop if asked to seek past the end of a file 35079: ALF-12490 "HTTP Status 500 - 00200935 Exception in Transaction" message error with webform - ALF-9524 fix assumed there were only switch elements in a form 35086: ALF-13563: Upgrade to Bitrock 8.1.0 to fix password validation issue 35095: ALF-12764: New distributable alfresco-enterprise-ear-3.4.9.zip - Like war zip, but contains .ear file instead of .wars and also contains WAS shared library - Means samples and other bits are finally available to non-Tomcat users 35103: Merged DEV to V3.4-BUG-FIX 35098: ALF-12776: if a user requests to join a moderated site, and that request is rejected, the rejection email is sent to the user-id and not the email id. Implemented Correct WorkflowModelModeratedInvitation.WF_PROP_REVIEW_COMMENTS field in configuration for moderatedInvitationReviewTask Person's email into emailAction PARAM_TO 35114: ALF-12766 Creating Web Content several users - different sandboxes - To be consistent with ALF-11440 PM comment 18-Dec-2011 and ALF-8787 A Manager should only be able to create a file in a sandbox if it is NOT locked somewhere else. - Not much can be done about the error message as the locked path is useful in other situations and it is not possible to issue a different message on create only 35121: ALF-11956: Merged BELARUS/V3.4-BUG-FIX-2012_04_05 to V3.4-BUG-FIX (V3.4.9) 35109: ALF-11956: WCM accessibility - Date/Time Pickers are made accessible via the keyboard and readable by JAWS (13, demo version). WAI-ARIA standard is used; - corrected 'expanded' state determination for Date/Time Pickers; - Modified Items and Web Forms arrow buttons are made accessible via the keyboard on the Browse Website page; - some changes per the description of the issue and per the comment of the 23-Feb-12 11:33 AM 35145: ALF-11990: CIFS login with case insensitive username is rejected - User name normalization moved to before MD4 hash retrieval 35151: Port of oplock related changes from v4.x. 35177: Fix for ALF-11936 - RSS feed from the activities dashlet produces invalid XML 35178: ALF-12631: removeChild requires delete permissions on the child node, even when it is a secondary association - now it doesn't (thanks to Andy's solution) - new ACL_PRI_CHILD_ASSOC_ON_CHILD ACL entry only enforces the permission on the child node when it is a primary association 35181: Merged DEV to V3.4-BUG-FIX 35165: ALF-13409: Invite to a site throws an error if an instance of invitation-moderated-workflow is started by a user whose account is subsequently deleted InvitationServiceImpl listens for person node deletions (it already implements beforeDeleteNode) and cancels invitations within beforeDeleteNode 35182: ALF-12567 Unable to create thumbnails for certain PDF files - The supplied PDF contains an invalid offset in the xref table. This turns out to be a quite common error resulting in thousands of Google hits. The offset is set to the string value "4294967295". This number in hex is FFFFFFFF. The value of an 4 byte int in C or Java with this value is -1. Neither PDFRenderer nor PDFBox have workarounds for this although lots of other systems do, which is why it is possible to view or edit it in other systems. Patched both PDFRenderer and PDFBox to handle this common error. 35185: ALF-13033: Friendlier error message when you try to delete non existent content from a sandbox 35191: ALF-13409: Fix build. 35192: Merged V3.4 to V3.4-BUG-FIX 35161: ALF-13624: Merged V4.0-BUG-FIX to V3.4 34474: ALF-13169 Tomcat fails to shut down - fix non daemon Timers (and punctuation!) 35163: ALF-13656: Merged HEAD to V3.4 31375: Fix for ALF-435 - Unfriendly error occurs when trying to delete renamed category from category page 35189: Italian translations from Gloria 35193: Merged V3.4 to V3.4-BUG-FIX (RECORD ONLY) 35125: Merged V3.4-BUG-FIX to V3.4 35156: Correction to merge in revision 35125 (a reintegrate merge rather than a selective merge) 35202: Merged V3.4-BUG-FIX to V4.0-BUG-FIX (RECORD ONLY) 34532: ALF-13233: Merged HEAD to V3.4-BUG-FIX 32960: ALF-11008 - Support the WebDAV DELETE method in SPP/VTI, with the special response required by SPP for locked documents 34559: ALF-13106: Merged HEAD to V3.4-BUG-FIX 28223: Merged DEV/SWIFT to HEAD (Tika and Poi) 30589: Upate Tika and add Ogg Vorbis support + tests 30673: Upgrade POI and Tika for recent fixes 31009: Bump the Tika version for some recent fixes 31010: Update the test audio files to include more metadata 31011: ALF-6170 Add missing audio model (needed in devcon demo) 31013: Update the MP3 extractor to output audio keys (related to ALF-6170), and refactor the audio extractors to share more common code. Also expands the audio extractor tests to share common code, and test more metadata. (Needed for devcon demo) 31022: Tika update for custom mimetypes enhancement 31023: Add @since tags where known, and do a quick coding standards sweep 31274: ALF-10813 follow-on - make it clearer that we're just creating the one detector, and switch to the new style version 31289: ALF-10803 - Upgrade Tika to add the extra WordPerfect mimetype 31553: ALF-10525 ACP mimetype detection fix, unit tests for it, and a NPE fix 31554: Update Tika to get the fix for TIKA-764 32105: ALF-11574 Upgrade Tika for the fix to TIKA-784, and add the DITA types to the Alfresco mimetype map 32138: Bump the Tika version for the updated TIKA-784 fix, and add an Alfresco side unit test for this case 32153: Update the vorbis jar to one that includes the license info more clearly in META-INF (without needing to read the POM) 32320: ALF-11650 Upgrade Tika for TIKA-789 (MPP Detection), and add tests that show it is now being correctly handled 32363: Update POI and Tika for the new code required to solve ALF-10980 (MPP Open/Change detection) 34560: ALF-13106: Merged V4.0-BUG-FIX to V3.4-BUG-FIX 33330: ALF-12487 In Mimetype Detection, if Tika detects a generic type of text/plain or XML, defer to the Alfresco filename based type (as we already do for octet stream) 33379: Add the TIFF mimetype 33380: Improve the stream to Tika conversion code, following review for THOR-952 33385: Upgrade to the latest Tika and POI, for recent bug fixes 33779: Upgrade Tika for ALF-12714 33782: ALF-12714 Add 3GPP/3GPP2 video, and MP4 Audio mimetypes 33783: Update Tika for more MP4/QuickTime support, and enable MP4 audio metadata extraction + "quick" testing 34561: ALF-13106: Fixed merge errors 34562: ALF-13106: Merged SWIFT to V3.4-BUG-FIX 26546: Have one copy of the Tika Config in spring, rather than several places fetching their own copy of the default one (either explicitly or implicitly). 34563: ALF-13106: Merged HEAD to V3.4-BUG-FIX 32264: Adding "quick" test resources for MS project. 34564: ALF-13106: Fix unit test 34752: GERMAN: Translation updates, based on EN: 34612 34753: SPANISH: Translation updates, based on EN: 34612 34755: FRENCH: Translation updates, based on EN: 34612 34756: ITALIAN: Translation updates, based on EN: 34612 34967: ALF-13552: Merged V4.0 to V3.4-BUG-FIX 34932: ALF-13453: Enable XMLConstants.FEATURE_SECURE_PROCESSING feature on Transformer Factory to prevent remote code execution - Now SecureTransformerFactory should be used as a standard 34971: ALF-13552: Merged V4.0 to V3.4-BUG-FIX 34968: ALF-13453: Reversed XSLTProcessor and XSLTRenderingEngine changes for now as they break http://wiki.alfresco.com/wiki/WCM_Forms_Rendering and model handling via bsf extensions. A more sophisticated approach is required. See bug for more info. 34982: ALF-13554: Merged V4.0 to V3.4-BUG-FIX 34972: ALF-13340: Upgrade postgres JDBC driver to tested/supported version! 34999: ALF-13552: Merged V4.0 to V3.4-BUG-FIX 34997: ALF-13453, ALF-13565: Fully reverted revision 34932 as it prevents startup on Weblogic 35000: Translation updates for DE, ES, IT. Based on EN r34846. 35015: ALF-13451: Merged V4.0-BUG-FIX to V3.4-BUG-FIX 33864: ALF-10736: JSF - Adding mimetype does not work on 3.4.x 35020: ALF-13451: Merged V4.0-BUG-FIX to V3.4-BUG-FIX 33863: ConfigSource for XMLConfigService which uses a ResourceFinder for wildcard-compatible lookups (UrlConfigSource does not support them) 35029: JAPANESE: Translation updates based on EN r34846 35212: ALF-13409: Deleting a person can now cancel their invitations. Cancelling invitations can delete inactive persons! So prevent infinite looping with a transaction local resource - Also fix up other invite related unit tests 35217: Merged DEV to V4.0-BUG-FIX 35214: ALF-12745 : AD-LDAP: alfresco hangs when upload user csv file Disable 'Upload User CSV File' button in Share admin console in case of AD-LDAP 35221: Avoid a NPE if Repository.getPerson() is called when no RunAsUser is active, instead return Null as for users with no defined NodeRef git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@35229 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
3817 lines
140 KiB
Java
3817 lines
140 KiB
Java
/*
|
|
* Copyright (C) 2005-2012 Alfresco Software Limited.
|
|
*
|
|
* This file is part of Alfresco
|
|
*
|
|
* Alfresco is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Alfresco is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
package org.alfresco.repo.jscript;
|
|
|
|
import java.io.BufferedInputStream;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.InputStreamReader;
|
|
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;
|
|
import java.util.LinkedHashMap;
|
|
import java.util.LinkedHashSet;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
import java.util.StringTokenizer;
|
|
|
|
import org.alfresco.error.AlfrescoRuntimeException;
|
|
import org.alfresco.model.ApplicationModel;
|
|
import org.alfresco.model.ContentModel;
|
|
import org.alfresco.query.PagingRequest;
|
|
import org.alfresco.query.PagingResults;
|
|
import org.alfresco.repo.action.executer.TransformActionExecuter;
|
|
import org.alfresco.repo.content.transform.magick.ImageTransformationOptions;
|
|
import org.alfresco.repo.model.filefolder.FileFolderServiceImpl.InvalidTypeException;
|
|
import org.alfresco.repo.search.QueryParameterDefImpl;
|
|
import org.alfresco.repo.security.permissions.AccessDeniedException;
|
|
import org.alfresco.repo.tagging.script.TagScope;
|
|
import org.alfresco.repo.thumbnail.ThumbnailDefinition;
|
|
import org.alfresco.repo.thumbnail.ThumbnailHelper;
|
|
import org.alfresco.repo.thumbnail.ThumbnailRegistry;
|
|
import org.alfresco.repo.thumbnail.script.ScriptThumbnail;
|
|
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
|
|
import org.alfresco.repo.version.VersionModel;
|
|
import org.alfresco.repo.workflow.jscript.JscriptWorkflowInstance;
|
|
import org.alfresco.scripts.ScriptException;
|
|
import org.alfresco.service.ServiceRegistry;
|
|
import org.alfresco.service.cmr.action.Action;
|
|
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
|
|
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
|
import org.alfresco.service.cmr.lock.LockStatus;
|
|
import org.alfresco.service.cmr.model.FileFolderService;
|
|
import org.alfresco.service.cmr.model.FileInfo;
|
|
import org.alfresco.service.cmr.model.FileNotFoundException;
|
|
import org.alfresco.service.cmr.repository.AssociationRef;
|
|
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
|
import org.alfresco.service.cmr.repository.ContentData;
|
|
import org.alfresco.service.cmr.repository.ContentReader;
|
|
import org.alfresco.service.cmr.repository.ContentService;
|
|
import org.alfresco.service.cmr.repository.ContentWriter;
|
|
import org.alfresco.service.cmr.repository.NodeRef;
|
|
import org.alfresco.service.cmr.repository.NodeService;
|
|
import org.alfresco.service.cmr.repository.Path;
|
|
import org.alfresco.service.cmr.repository.StoreRef;
|
|
import org.alfresco.service.cmr.repository.TemplateImageResolver;
|
|
import org.alfresco.service.cmr.repository.TransformationOptions;
|
|
import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
|
|
import org.alfresco.service.cmr.search.QueryParameterDefinition;
|
|
import org.alfresco.service.cmr.security.AccessPermission;
|
|
import org.alfresco.service.cmr.security.AccessStatus;
|
|
import org.alfresco.service.cmr.security.PermissionService;
|
|
import org.alfresco.service.cmr.thumbnail.ThumbnailService;
|
|
import org.alfresco.service.cmr.version.Version;
|
|
import org.alfresco.service.cmr.version.VersionHistory;
|
|
import org.alfresco.service.cmr.version.VersionType;
|
|
import org.alfresco.service.cmr.workflow.WorkflowInstance;
|
|
import org.alfresco.service.cmr.workflow.WorkflowService;
|
|
import org.alfresco.service.namespace.NamespaceException;
|
|
import org.alfresco.service.namespace.NamespacePrefixResolver;
|
|
import org.alfresco.service.namespace.NamespacePrefixResolverProvider;
|
|
import org.alfresco.service.namespace.NamespaceService;
|
|
import org.alfresco.service.namespace.QName;
|
|
import org.alfresco.service.namespace.RegexQNamePattern;
|
|
import org.alfresco.util.FileFilterMode;
|
|
import org.alfresco.util.GUID;
|
|
import org.alfresco.util.ISO8601DateFormat;
|
|
import org.alfresco.util.ISO9075;
|
|
import org.alfresco.util.Pair;
|
|
import org.alfresco.util.FileFilterMode.Client;
|
|
import org.apache.commons.logging.Log;
|
|
import org.apache.commons.logging.LogFactory;
|
|
import org.json.JSONException;
|
|
import org.json.JSONObject;
|
|
import org.mozilla.javascript.Context;
|
|
import org.mozilla.javascript.Scriptable;
|
|
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;
|
|
|
|
/**
|
|
* Script Node class implementation, specific for use by ScriptService as part of the object model.
|
|
* <p>
|
|
* The class exposes Node properties, children and assocs as dynamically populated maps and lists. The various collection classes are mirrored as JavaScript properties. So can be
|
|
* accessed using standard JavaScript property syntax, such as <code>node.children[0].properties.name</code>.
|
|
* <p>
|
|
* Various helper methods are provided to access common and useful node variables such as the content url and type information.
|
|
*
|
|
* @author Kevin Roast
|
|
*/
|
|
public class ScriptNode implements Scopeable, NamespacePrefixResolverProvider
|
|
{
|
|
private static final long serialVersionUID = -3378946227712939601L;
|
|
|
|
private static Log logger = LogFactory.getLog(ScriptNode.class);
|
|
|
|
private final static String NAMESPACE_BEGIN = "" + QName.NAMESPACE_BEGIN;
|
|
|
|
private final static String CONTENT_DEFAULT_URL = "/d/d/{0}/{1}/{2}/{3}";
|
|
private final static String CONTENT_DOWNLOAD_URL = "/d/a/{0}/{1}/{2}/{3}";
|
|
private final static String CONTENT_PROP_URL = "/d/d/{0}/{1}/{2}/{3}?property={4}";
|
|
private final static String CONTENT_DOWNLOAD_PROP_URL = "/d/a/{0}/{1}/{2}/{3}?property={4}";
|
|
private final static String FOLDER_BROWSE_URL = "/n/browse/{0}/{1}/{2}";
|
|
|
|
/** Root scope for this object */
|
|
protected Scriptable scope;
|
|
|
|
/** Node Value Converter */
|
|
protected NodeValueConverter converter = null;
|
|
|
|
/** Cached values */
|
|
protected NodeRef nodeRef;
|
|
|
|
private FileInfo nodeInfo;
|
|
|
|
private String name;
|
|
private QName type;
|
|
protected String id;
|
|
protected String siteName;
|
|
protected boolean siteNameResolved = false;
|
|
|
|
/** The aspects applied to this node */
|
|
protected Set<QName> aspects = null;
|
|
|
|
/** The target associations from this node */
|
|
private ScriptableQNameMap<String, Object> targetAssocs = null;
|
|
|
|
/** The source associations to this node */
|
|
private ScriptableQNameMap<String, Object> sourceAssocs = null;
|
|
|
|
/** The child associations for this node */
|
|
private ScriptableQNameMap<String, Object> childAssocs = null;
|
|
|
|
/** The children of this node */
|
|
private Scriptable children = null;
|
|
|
|
/** The properties of this node */
|
|
private ScriptableQNameMap<String, Serializable> properties = null;
|
|
|
|
/** The versions of this node */
|
|
private Scriptable versions = null;
|
|
|
|
/** The active workflows acting on this node */
|
|
private Scriptable activeWorkflows = null;
|
|
|
|
protected ServiceRegistry services = null;
|
|
private NodeService nodeService = null;
|
|
private FileFolderService fileFolderService = null;
|
|
private Boolean isDocument = null;
|
|
private Boolean isContainer = null;
|
|
private Boolean isLinkToDocument = null;
|
|
private Boolean isLinkToContainer = null;
|
|
private Boolean hasChildren = null;
|
|
private String displayPath = null;
|
|
protected TemplateImageResolver imageResolver = null;
|
|
protected ScriptNode parent = null;
|
|
private ChildAssociationRef primaryParentAssoc = null;
|
|
private ScriptableQNameMap<String, Object> parentAssocs = null;
|
|
// NOTE: see the reset() method when adding new cached members!
|
|
|
|
|
|
// ------------------------------------------------------------------------------
|
|
// Construction
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @param nodeRef The NodeRef this Node wrapper represents
|
|
* @param services The ServiceRegistry the Node can use to access services
|
|
*/
|
|
public ScriptNode(NodeRef nodeRef, ServiceRegistry services)
|
|
{
|
|
this(nodeRef, services, null);
|
|
}
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @param nodeInfo The FileInfo this Node wrapper represents
|
|
* @param services The ServiceRegistry the Node can use to access services
|
|
* @param scope Root scope for this Node
|
|
*/
|
|
public ScriptNode(FileInfo nodeInfo, ServiceRegistry services, Scriptable scope)
|
|
{
|
|
this(nodeInfo.getNodeRef(), services, scope);
|
|
|
|
this.nodeInfo = nodeInfo;
|
|
}
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @param nodeRef The NodeRef this Node wrapper represents
|
|
* @param services The ServiceRegistry the Node can use to access services
|
|
* @param scope Root scope for this Node
|
|
*/
|
|
public ScriptNode(NodeRef nodeRef, ServiceRegistry services, Scriptable scope)
|
|
{
|
|
if (nodeRef == null)
|
|
{
|
|
throw new IllegalArgumentException("NodeRef must be supplied.");
|
|
}
|
|
|
|
if (services == null)
|
|
{
|
|
throw new IllegalArgumentException("The ServiceRegistry must be supplied.");
|
|
}
|
|
|
|
this.nodeRef = nodeRef;
|
|
this.id = nodeRef.getId();
|
|
this.services = services;
|
|
this.nodeService = services.getNodeService();
|
|
this.fileFolderService = services.getFileFolderService();
|
|
this.scope = scope;
|
|
}
|
|
|
|
@Override
|
|
public int hashCode()
|
|
{
|
|
final int PRIME = 31;
|
|
int result = 1;
|
|
result = PRIME * result + ((nodeRef == null) ? 0 : nodeRef.hashCode());
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object obj)
|
|
{
|
|
if (this == obj) return true;
|
|
if (obj == null) return false;
|
|
if (getClass() != obj.getClass()) return false;
|
|
if (!nodeRef.equals(((ScriptNode)obj).nodeRef)) return false;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Factory method
|
|
*/
|
|
public ScriptNode newInstance(NodeRef nodeRef, ServiceRegistry services, Scriptable scope)
|
|
{
|
|
return new ScriptNode(nodeRef, services, scope);
|
|
}
|
|
|
|
public ScriptNode newInstance(FileInfo nodeInfo, ServiceRegistry services, Scriptable scope)
|
|
{
|
|
return new ScriptNode(nodeInfo, services, scope);
|
|
}
|
|
|
|
/**
|
|
* @see org.alfresco.repo.jscript.Scopeable#setScope(org.mozilla.javascript.Scriptable)
|
|
*/
|
|
public void setScope(Scriptable scope)
|
|
{
|
|
this.scope = scope;
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------------------
|
|
// Node Wrapper API
|
|
|
|
/**
|
|
* @return The GUID for the node
|
|
*/
|
|
public String getId()
|
|
{
|
|
return this.id;
|
|
}
|
|
|
|
/**
|
|
* @return the store type for the node
|
|
*/
|
|
public String getStoreType()
|
|
{
|
|
return this.nodeRef.getStoreRef().getProtocol();
|
|
}
|
|
|
|
/**
|
|
* @return the store id for the node
|
|
*/
|
|
public String getStoreId()
|
|
{
|
|
return this.nodeRef.getStoreRef().getIdentifier();
|
|
}
|
|
|
|
/**
|
|
* @return Returns the NodeRef this Node object represents
|
|
*/
|
|
public NodeRef getNodeRef()
|
|
{
|
|
return this.nodeRef;
|
|
}
|
|
|
|
/**
|
|
* @return Returns the QName type.
|
|
*/
|
|
public QName getQNameType()
|
|
{
|
|
if (this.type == null)
|
|
{
|
|
this.type = this.nodeService.getType(this.nodeRef);
|
|
}
|
|
|
|
return type;
|
|
}
|
|
|
|
/**
|
|
* @return Returns the type.
|
|
*/
|
|
public String getType()
|
|
{
|
|
return getQNameType().toString();
|
|
}
|
|
|
|
/**
|
|
* @return Returns the type in short format.
|
|
*/
|
|
public String getTypeShort()
|
|
{
|
|
return this.getShortQName(getQNameType());
|
|
}
|
|
|
|
/**
|
|
* @return Helper to return the 'name' property for the node
|
|
*/
|
|
public String getName()
|
|
{
|
|
if (this.name == null)
|
|
{
|
|
// try and get the name from the properties first
|
|
this.name = (String) getProperties().get("cm:name");
|
|
|
|
// if we didn't find it as a property get the name from the association name
|
|
if (this.name == null)
|
|
{
|
|
ChildAssociationRef parentRef = this.nodeService.getPrimaryParent(this.nodeRef);
|
|
if (parentRef != null && parentRef.getQName() != null)
|
|
{
|
|
this.name = parentRef.getQName().getLocalName();
|
|
}
|
|
else
|
|
{
|
|
this.name = "";
|
|
}
|
|
}
|
|
}
|
|
|
|
return this.name;
|
|
}
|
|
|
|
/**
|
|
* Helper to set the 'name' property for the node.
|
|
*
|
|
* @param name Name to set
|
|
*/
|
|
public void setName(String name)
|
|
{
|
|
if (name != null)
|
|
{
|
|
QName typeQName = getQNameType();
|
|
if ((services.getDictionaryService().isSubClass(typeQName, ContentModel.TYPE_FOLDER) &&
|
|
!services.getDictionaryService().isSubClass(typeQName, ContentModel.TYPE_SYSTEM_FOLDER)) ||
|
|
services.getDictionaryService().isSubClass(typeQName, ContentModel.TYPE_CONTENT))
|
|
{
|
|
try
|
|
{
|
|
this.services.getFileFolderService().rename(this.nodeRef, name);
|
|
}
|
|
catch (FileNotFoundException e)
|
|
{
|
|
throw new AlfrescoRuntimeException("Failed to rename node " + nodeRef + " to " + name, e);
|
|
}
|
|
}
|
|
this.getProperties().put(ContentModel.PROP_NAME.toString(), name.toString());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return The children of this Node as JavaScript array of Node object wrappers
|
|
*/
|
|
public Scriptable getChildren()
|
|
{
|
|
if (this.children == null)
|
|
{
|
|
List<ChildAssociationRef> childRefs = this.nodeService.getChildAssocs(this.nodeRef);
|
|
Object[] children = new Object[childRefs.size()];
|
|
for (int i = 0; i < childRefs.size(); i++)
|
|
{
|
|
// 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);
|
|
}
|
|
|
|
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<Object>(){
|
|
@Override
|
|
public int compare(Object o1, Object o2)
|
|
{
|
|
return col.compare(((ScriptNode)o1).getName(), ((ScriptNode)o2).getName());
|
|
}});
|
|
}
|
|
|
|
/**
|
|
* @return true if the Node has children
|
|
*/
|
|
public boolean getHasChildren()
|
|
{
|
|
if (this.hasChildren == null)
|
|
{
|
|
this.hasChildren = !this.services.getNodeService().getChildAssocs(
|
|
getNodeRef(), RegexQNamePattern.MATCH_ALL, RegexQNamePattern.MATCH_ALL, false).isEmpty();
|
|
}
|
|
return hasChildren;
|
|
}
|
|
|
|
/**
|
|
* childByNamePath returns the Node at the specified 'cm:name' based Path walking the children of this Node.
|
|
* So a valid call might be:
|
|
* <code>mynode.childByNamePath("/QA/Testing/Docs");</code>
|
|
*
|
|
* is a leading / required? No, but it can be specified.
|
|
* are wild-cards supported? Does not seem to be used anywhere
|
|
*
|
|
* @return The ScriptNode or null if the node is not found.
|
|
*/
|
|
public ScriptNode childByNamePath(String path)
|
|
{
|
|
ScriptNode child = null;
|
|
|
|
if (this.services.getDictionaryService().isSubClass(getQNameType(), ContentModel.TYPE_FOLDER))
|
|
{
|
|
/**
|
|
* The current node is a folder.
|
|
* optimized code path for cm:folder and sub-types supporting getChildrenByName() method
|
|
*/
|
|
NodeRef result = null;
|
|
StringTokenizer t = new StringTokenizer(path, "/");
|
|
if (t.hasMoreTokens())
|
|
{
|
|
result = this.nodeRef;
|
|
while (t.hasMoreTokens() && result != null)
|
|
{
|
|
String name = t.nextToken();
|
|
try
|
|
{
|
|
result = this.nodeService.getChildByName(result, ContentModel.ASSOC_CONTAINS, name);
|
|
}
|
|
catch (AccessDeniedException ade)
|
|
{
|
|
result = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
child = (result != null ? newInstance(result, this.services, this.scope) : null);
|
|
}
|
|
else
|
|
{
|
|
/**
|
|
* The current node is not a folder. Perhaps it is Company Home ?
|
|
*/
|
|
// convert the name based path to a valid XPath query
|
|
StringBuilder xpath = new StringBuilder(path.length() << 1);
|
|
StringTokenizer t = new StringTokenizer(path, "/");
|
|
int count = 0;
|
|
QueryParameterDefinition[] params = new QueryParameterDefinition[t.countTokens()];
|
|
DataTypeDefinition ddText =
|
|
this.services.getDictionaryService().getDataType(DataTypeDefinition.TEXT);
|
|
NamespaceService ns = this.services.getNamespaceService();
|
|
while (t.hasMoreTokens())
|
|
{
|
|
if (xpath.length() != 0)
|
|
{
|
|
xpath.append('/');
|
|
}
|
|
String strCount = Integer.toString(count);
|
|
xpath.append("*[@cm:name=$cm:name")
|
|
.append(strCount)
|
|
.append(']');
|
|
params[count++] = new QueryParameterDefImpl(
|
|
QName.createQName(NamespaceService.CONTENT_MODEL_PREFIX, "name" + strCount, ns),
|
|
ddText,
|
|
true,
|
|
t.nextToken());
|
|
}
|
|
|
|
Object[] nodes = getChildrenByXPath(xpath.toString(), params, true);
|
|
|
|
child = (nodes.length != 0) ? (ScriptNode)nodes[0] : null;
|
|
}
|
|
|
|
return child;
|
|
}
|
|
|
|
/**
|
|
* @return Returns a JavaScript array of Nodes at the specified XPath starting at this Node.
|
|
* So a valid call might be <code>mynode.childrenByXPath("*[@cm:name='Testing']/*");</code>
|
|
*/
|
|
public Scriptable childrenByXPath(String xpath)
|
|
{
|
|
return Context.getCurrentContext().newArray(this.scope, getChildrenByXPath(xpath, null, false));
|
|
}
|
|
|
|
/**
|
|
* @return Returns a JavaScript array of child file/folder nodes for this nodes.
|
|
* Automatically retrieves all sub-types of cm:content and cm:folder, also removes
|
|
* system folder types from the results.
|
|
* This is equivalent to @see FileFolderService.list()
|
|
*/
|
|
public Scriptable childFileFolders()
|
|
{
|
|
return childFileFolders(true, true, null);
|
|
}
|
|
|
|
/**
|
|
* @param files Return files extending from cm:content
|
|
* @param folders Return folders extending from cm:folder - ignoring sub-types of cm:systemfolder
|
|
*
|
|
* @return Returns a JavaScript array of child file/folder nodes for this nodes.
|
|
* Automatically retrieves all sub-types of cm:content and cm:folder, also removes
|
|
* system folder types from the results.
|
|
* This is equivalent to @see FileFolderService.listFiles() and @see FileFolderService.listFolders()
|
|
*/
|
|
public Scriptable childFileFolders(boolean files, boolean folders)
|
|
{
|
|
return childFileFolders(files, folders, null);
|
|
}
|
|
|
|
/**
|
|
* @param files Return files extending from cm:content
|
|
* @param folders Return folders extending from cm:folder - ignoring sub-types of cm:systemfolder
|
|
* @param ignoreTypes Also optionally removes additional type qnames. The additional type can be
|
|
* specified in short or long qname string form as a single string or an Array e.g. "fm:forum".
|
|
*
|
|
* @return Returns a JavaScript array of child file/folder nodes for this nodes.
|
|
* Automatically retrieves all sub-types of cm:content and cm:folder, also removes
|
|
* system folder types from the results.
|
|
* This is equivalent to @see FileFolderService.listFiles() and @see FileFolderService.listFolders()
|
|
*/
|
|
public Scriptable childFileFolders(boolean files, boolean folders, Object ignoreTypes)
|
|
{
|
|
return childFileFolders(files, folders, ignoreTypes, -1, -1, 0, null, null, null).getPage();
|
|
}
|
|
|
|
/**
|
|
* @param files Return files extending from cm:content
|
|
* @param folders Return folders extending from cm:folder - ignoring sub-types of cm:systemfolder
|
|
* @param ignoreTypes Also optionally removes additional type qnames. The additional type can be
|
|
* specified in short or long qname string form as a single string or an Array e.g. "fm:forum".
|
|
* @param maxItems Max number of items
|
|
*
|
|
* @return Returns ScriptPagingNodes which includes a JavaScript array of child file/folder nodes for this nodes.
|
|
* Automatically retrieves all sub-types of cm:content and cm:folder, also removes
|
|
* system folder types from the results.
|
|
* This is equivalent to @see FileFolderService.listFiles() and @see FileFolderService.listFolders()
|
|
*
|
|
* @deprecated API for review (subject to change prior to release)
|
|
*
|
|
* @author janv
|
|
* @since 4.0
|
|
*/
|
|
public ScriptPagingNodes childFileFolders(boolean files, boolean folders, Object ignoreTypes, int maxItems)
|
|
{
|
|
return childFileFolders(files, folders, ignoreTypes, 0, maxItems, 0, null, null, null);
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
/**
|
|
* @param files Return files extending from cm:content
|
|
* @param folders Return folders extending from cm:folder - ignoring sub-types of cm:systemfolder
|
|
* @param ignoreTypes Also optionally removes additional type qnames. The additional type can be
|
|
* specified in short or long qname string form as a single string or an Array e.g. "fm:forum".
|
|
* @param skipOffset Items to skip (e.g. 0 or (num pages to skip * size of page)
|
|
* @param maxItems Max number of items (eg. size of page)
|
|
* @param requestTotalCountMax Request total count (upto a given max total count)
|
|
* Note: if 0 then total count is not requested and the query may be able to optimise/cutoff for max items)
|
|
* @param sortProp Optional sort property as a prefix qname string (e.g. "cm:name"). Also supports special
|
|
* content case (i.e. "cm:content.size" and "cm:content.mimetype")
|
|
* @param sortAsc Given a sort property, true => ascending, false => descending
|
|
* @param queryExecutionId If paging then can pass back the previous query execution (as a hint for possible query optimisation)
|
|
*
|
|
* @return Returns ScriptPagingNodes which includes a JavaScript array of child file/folder nodes for this nodes.
|
|
* Automatically retrieves all sub-types of cm:content and cm:folder, also removes
|
|
* system folder types from the results.
|
|
* This is equivalent to @see FileFolderService.listFiles() and @see FileFolderService.listFolders()
|
|
*
|
|
* @author janv
|
|
* @since 4.0
|
|
*/
|
|
public ScriptPagingNodes childFileFolders(boolean files, boolean folders, Object ignoreTypes, int skipOffset, int maxItems, int requestTotalCountMax, String sortProp, Boolean sortAsc, String queryExecutionId)
|
|
{
|
|
Object[] results;
|
|
|
|
Set<QName> ignoreTypeQNames = new HashSet<QName>(5);
|
|
|
|
// Add user defined types to ignore
|
|
if (ignoreTypes instanceof ScriptableObject)
|
|
{
|
|
Serializable types = getValueConverter().convertValueForRepo((ScriptableObject)ignoreTypes);
|
|
if (types instanceof List)
|
|
{
|
|
for (Serializable typeObj : (List<Serializable>)types)
|
|
{
|
|
ignoreTypeQNames.add(createQName(typeObj.toString()));
|
|
}
|
|
}
|
|
else if (types instanceof String)
|
|
{
|
|
ignoreTypeQNames.add(createQName(types.toString()));
|
|
}
|
|
}
|
|
else if (ignoreTypes instanceof String)
|
|
{
|
|
ignoreTypeQNames.add(createQName(ignoreTypes.toString()));
|
|
}
|
|
|
|
List<Pair<QName, Boolean>> sortProps = null; // note: null sortProps => get all in default sort order
|
|
if (sortProp != null)
|
|
{
|
|
sortProps = new ArrayList<Pair<QName, Boolean>>(1);
|
|
sortProps.add(new Pair<QName, Boolean>(createQName(sortProp), sortAsc));
|
|
}
|
|
|
|
PagingRequest pageRequest = new PagingRequest(skipOffset, maxItems, queryExecutionId);
|
|
pageRequest.setRequestTotalCountMax(requestTotalCountMax);
|
|
|
|
PagingResults<FileInfo> pageOfNodeInfos = null;
|
|
FileFilterMode.setClient(Client.script);
|
|
try
|
|
{
|
|
pageOfNodeInfos = this.fileFolderService.list(this.nodeRef, files, folders, null, ignoreTypeQNames, sortProps, pageRequest);
|
|
}
|
|
finally
|
|
{
|
|
FileFilterMode.clearClient();
|
|
}
|
|
|
|
List<FileInfo> nodeInfos = pageOfNodeInfos.getPage();
|
|
int size = nodeInfos.size();
|
|
results = new Object[size];
|
|
for (int i=0; i<size; i++)
|
|
{
|
|
FileInfo nodeInfo = nodeInfos.get(i);
|
|
results[i] = newInstance(nodeInfo, this.services, this.scope);
|
|
}
|
|
|
|
int totalResultCountLower = -1;
|
|
int totalResultCountUpper = -1;
|
|
|
|
Pair<Integer, Integer> totalResultCount = pageOfNodeInfos.getTotalResultCount();
|
|
if (totalResultCount != null)
|
|
{
|
|
totalResultCountLower = (totalResultCount.getFirst() != null ? totalResultCount.getFirst() : -1);
|
|
totalResultCountUpper = (totalResultCount.getSecond() != null ? totalResultCount.getSecond() : -1);
|
|
}
|
|
|
|
return new ScriptPagingNodes(Context.getCurrentContext().newArray(this.scope, results), pageOfNodeInfos.hasMoreItems(), totalResultCountLower, totalResultCountUpper);
|
|
}
|
|
|
|
/**
|
|
* Return the target associations from this Node. As a Map of assoc type to a JavaScript array of Nodes.
|
|
* The Map returned implements the Scriptable interface to allow access to the assoc arrays via JavaScript
|
|
* associative array access. This means associations of this node can be access thus:
|
|
* <code>node.assocs["translations"][0]</code>
|
|
*
|
|
* @return target associations as a Map of assoc name to a JavaScript array of Nodes.
|
|
*/
|
|
@SuppressWarnings("unchecked")
|
|
public Map<String, Object> getAssocs()
|
|
{
|
|
if (this.targetAssocs == null)
|
|
{
|
|
// this Map implements the Scriptable interface for native JS syntax property access
|
|
this.targetAssocs = new ScriptableQNameMap<String, Object>(this);
|
|
|
|
// get the list of target nodes for each association type
|
|
List<AssociationRef> refs = this.nodeService.getTargetAssocs(this.nodeRef, RegexQNamePattern.MATCH_ALL);
|
|
for (AssociationRef ref : refs)
|
|
{
|
|
String qname = ref.getTypeQName().toString();
|
|
List<ScriptNode> nodes = (List<ScriptNode>)this.targetAssocs.get(qname);
|
|
if (nodes == null)
|
|
{
|
|
// first access of the list for this qname
|
|
nodes = new ArrayList<ScriptNode>(4);
|
|
}
|
|
this.targetAssocs.put(ref.getTypeQName().toString(), nodes);
|
|
nodes.add(newInstance(ref.getTargetRef(), this.services, this.scope));
|
|
}
|
|
|
|
// convert each Node list into a JavaScript array object
|
|
for (String qname : this.targetAssocs.keySet())
|
|
{
|
|
List<ScriptNode> nodes = (List<ScriptNode>)this.targetAssocs.get(qname);
|
|
Object[] objs = nodes.toArray(new Object[nodes.size()]);
|
|
this.targetAssocs.put(qname, Context.getCurrentContext().newArray(this.scope, objs));
|
|
}
|
|
}
|
|
|
|
return this.targetAssocs;
|
|
}
|
|
|
|
public Map<String, Object> getAssociations()
|
|
{
|
|
return getAssocs();
|
|
}
|
|
|
|
/**
|
|
* Return the source associations to this Node. As a Map of assoc name to a JavaScript array of Nodes.
|
|
* The Map returned implements the Scriptable interface to allow access to the assoc arrays via JavaScript
|
|
* associative array access. This means source associations to this node can be access thus:
|
|
* <code>node.sourceAssocs["translations"][0]</code>
|
|
*
|
|
* @return source associations as a Map of assoc name to a JavaScript array of Nodes.
|
|
*/
|
|
@SuppressWarnings("unchecked")
|
|
public Map<String, Object> getSourceAssocs()
|
|
{
|
|
if (this.sourceAssocs == null)
|
|
{
|
|
// this Map implements the Scriptable interface for native JS syntax property access
|
|
this.sourceAssocs = new ScriptableQNameMap<String, Object>(this);
|
|
|
|
// get the list of source nodes for each association type
|
|
List<AssociationRef> refs = this.nodeService.getSourceAssocs(this.nodeRef, RegexQNamePattern.MATCH_ALL);
|
|
for (AssociationRef ref : refs)
|
|
{
|
|
String qname = ref.getTypeQName().toString();
|
|
List<ScriptNode> nodes = (List<ScriptNode>)this.sourceAssocs.get(qname);
|
|
if (nodes == null)
|
|
{
|
|
// first access of the list for this qname
|
|
nodes = new ArrayList<ScriptNode>(4);
|
|
this.sourceAssocs.put(ref.getTypeQName().toString(), nodes);
|
|
}
|
|
nodes.add(newInstance(ref.getSourceRef(), this.services, this.scope));
|
|
}
|
|
|
|
// convert each Node list into a JavaScript array object
|
|
for (String qname : this.sourceAssocs.keySet())
|
|
{
|
|
List<ScriptNode> nodes = (List<ScriptNode>)this.sourceAssocs.get(qname);
|
|
Object[] objs = nodes.toArray(new Object[nodes.size()]);
|
|
this.sourceAssocs.put(qname, Context.getCurrentContext().newArray(this.scope, objs));
|
|
}
|
|
}
|
|
|
|
return this.sourceAssocs;
|
|
}
|
|
|
|
public Map<String, Object> getSourceAssociations()
|
|
{
|
|
return getSourceAssocs();
|
|
}
|
|
|
|
/**
|
|
* Return the child associations from this Node. As a Map of assoc name to a JavaScript array of Nodes.
|
|
* The Map returned implements the Scriptable interface to allow access to the assoc arrays via JavaScript
|
|
* associative array access. This means associations of this node can be access thus:
|
|
* <code>node.childAssocs["contains"][0]</code>
|
|
*
|
|
* @return child associations as a Map of assoc name to a JavaScript array of Nodes.
|
|
*/
|
|
@SuppressWarnings("unchecked")
|
|
public Map<String, Object> getChildAssocs()
|
|
{
|
|
if (this.childAssocs == null)
|
|
{
|
|
// this Map implements the Scriptable interface for native JS syntax property access
|
|
this.childAssocs = new ScriptableQNameMap<String, Object>(this);
|
|
|
|
// get the list of child assoc nodes for each association type
|
|
List<ChildAssociationRef> refs = this.nodeService.getChildAssocs(nodeRef);
|
|
for (ChildAssociationRef ref : refs)
|
|
{
|
|
String qname = ref.getTypeQName().toString();
|
|
List<ScriptNode> nodes = (List<ScriptNode>)this.childAssocs.get(qname);
|
|
if (nodes == null)
|
|
{
|
|
// first access of the list for this qname
|
|
nodes = new ArrayList<ScriptNode>(4);
|
|
this.childAssocs.put(ref.getTypeQName().toString(), nodes);
|
|
}
|
|
nodes.add(newInstance(ref.getChildRef(), this.services, this.scope));
|
|
}
|
|
|
|
// convert each Node list into a JavaScript array object
|
|
for (String qname : this.childAssocs.keySet())
|
|
{
|
|
List<ScriptNode> nodes = (List<ScriptNode>)this.childAssocs.get(qname);
|
|
Object[] objs = nodes.toArray(new Object[nodes.size()]);
|
|
this.childAssocs.put(qname, Context.getCurrentContext().newArray(this.scope, objs));
|
|
}
|
|
}
|
|
|
|
return this.childAssocs;
|
|
}
|
|
|
|
public Map<String, Object> getChildAssociations()
|
|
{
|
|
return getChildAssocs();
|
|
}
|
|
|
|
/**
|
|
* Return an Array of the associations from this Node that match a specific object type.
|
|
* <code>node.getChildAssocsByType("cm:folder")[0]</code>
|
|
*
|
|
* @return Array of child associations from this Node that match a specific object type.
|
|
*/
|
|
public Scriptable getChildAssocsByType(String type)
|
|
{
|
|
// get the list of child assoc nodes for each association type
|
|
Set<QName> types = new HashSet<QName>(1, 1.0f);
|
|
types.add(createQName(type));
|
|
List<ChildAssociationRef> refs = this.nodeService.getChildAssocs(this.nodeRef, types);
|
|
Object[] nodes = new Object[refs.size()];
|
|
for (int i=0; i<nodes.length; i++)
|
|
{
|
|
ChildAssociationRef ref = refs.get(i);
|
|
nodes[i] = newInstance(ref.getChildRef(), this.services, this.scope);
|
|
}
|
|
return Context.getCurrentContext().newArray(this.scope, nodes);
|
|
}
|
|
|
|
/**
|
|
* Return the parent associations to this Node. As a Map of assoc name to a JavaScript array of Nodes.
|
|
* The Map returned implements the Scriptable interface to allow access to the assoc arrays via JavaScript
|
|
* associative array access. This means associations of this node can be access thus:
|
|
* <code>node.parentAssocs["contains"][0]</code>
|
|
*
|
|
* @return parent associations as a Map of assoc name to a JavaScript array of Nodes.
|
|
*/
|
|
@SuppressWarnings("unchecked")
|
|
public Map<String, Object> getParentAssocs()
|
|
{
|
|
if (this.parentAssocs == null)
|
|
{
|
|
// this Map implements the Scriptable interface for native JS syntax property access
|
|
this.parentAssocs = new ScriptableQNameMap<String, Object>(this);
|
|
|
|
// get the list of child assoc nodes for each association type
|
|
List<ChildAssociationRef> refs = this.nodeService.getParentAssocs(nodeRef);
|
|
for (ChildAssociationRef ref : refs)
|
|
{
|
|
String qname = ref.getTypeQName().toString();
|
|
List<ScriptNode> nodes = (List<ScriptNode>)this.parentAssocs.get(qname);
|
|
if (nodes == null)
|
|
{
|
|
// first access of the list for this qname
|
|
nodes = new ArrayList<ScriptNode>(4);
|
|
this.parentAssocs.put(ref.getTypeQName().toString(), nodes);
|
|
}
|
|
nodes.add(newInstance(ref.getParentRef(), this.services, this.scope));
|
|
}
|
|
|
|
// convert each Node list into a JavaScript array object
|
|
for (String qname : this.parentAssocs.keySet())
|
|
{
|
|
List<ScriptNode> nodes = (List<ScriptNode>)this.parentAssocs.get(qname);
|
|
Object[] objs = nodes.toArray(new Object[nodes.size()]);
|
|
this.parentAssocs.put(qname, Context.getCurrentContext().newArray(this.scope, objs));
|
|
}
|
|
}
|
|
|
|
return this.parentAssocs;
|
|
}
|
|
|
|
public Map<String, Object> getParentAssociations()
|
|
{
|
|
return getParentAssocs();
|
|
}
|
|
|
|
/**
|
|
* Checks whether the {@link ScriptNode} exists in the repository.
|
|
* @return
|
|
*/
|
|
public boolean exists()
|
|
{
|
|
return nodeService.exists(nodeRef);
|
|
}
|
|
|
|
/**
|
|
* Return all the properties known about this node. The Map returned implements the Scriptable interface to
|
|
* allow access to the properties via JavaScript associative array access. This means properties of a node can
|
|
* be access thus: <code>node.properties["name"]</code>
|
|
*
|
|
* @return Map of properties for this Node.
|
|
*/
|
|
@SuppressWarnings("unchecked")
|
|
public Map<String, Object> getProperties()
|
|
{
|
|
if (this.properties == null)
|
|
{
|
|
// this Map implements the Scriptable interface for native JS syntax property access
|
|
// this impl of the QNameMap is capable of creating ScriptContentData on demand for 'cm:content'
|
|
// properties that have not been initialised - see AR-1673.
|
|
this.properties = new ContentAwareScriptableQNameMap<String, Serializable>(this, this.services);
|
|
|
|
Map<QName, Serializable> props = null;
|
|
if (nodeInfo != null)
|
|
{
|
|
props = nodeInfo.getProperties();
|
|
}
|
|
else
|
|
{
|
|
props = this.nodeService.getProperties(this.nodeRef);
|
|
}
|
|
|
|
for (QName qname : props.keySet())
|
|
{
|
|
Serializable propValue = props.get(qname);
|
|
|
|
// perform the conversion to a script safe value and store
|
|
this.properties.put(qname.toString(), getValueConverter().convertValueForScript(qname, propValue));
|
|
}
|
|
}
|
|
|
|
return this.properties;
|
|
}
|
|
|
|
/**
|
|
* Return all the property names defined for this node's type as an array of short QNames.
|
|
*
|
|
* @return Array of property names for this node's type.
|
|
*/
|
|
public Scriptable getTypePropertyNames()
|
|
{
|
|
return getTypePropertyNames(true);
|
|
}
|
|
|
|
/**
|
|
* Return all the property names defined for this node's type as an array.
|
|
*
|
|
* @param useShortQNames if true short-form qnames will be returned, else long-form.
|
|
* @return Array of property names for this node's type.
|
|
*/
|
|
public Scriptable getTypePropertyNames(boolean useShortQNames)
|
|
{
|
|
Set<QName> props = this.services.getDictionaryService().getClass(this.getQNameType()).getProperties().keySet();
|
|
Object[] result = new Object[props.size()];
|
|
int count = 0;
|
|
for (QName qname : props)
|
|
{
|
|
result[count++] = useShortQNames ? getShortQName(qname).toString() : qname.toString();
|
|
}
|
|
return Context.getCurrentContext().newArray(this.scope, result);
|
|
}
|
|
|
|
/**
|
|
* Return all the property names defined for this node as an array.
|
|
*
|
|
* @param useShortQNames if true short-form qnames will be returned, else long-form.
|
|
* @return Array of property names for this node type and optionally parent properties.
|
|
*/
|
|
public Scriptable getPropertyNames(boolean useShortQNames)
|
|
{
|
|
Set<QName> props = this.nodeService.getProperties(this.nodeRef).keySet();
|
|
Object[] result = new Object[props.size()];
|
|
int count = 0;
|
|
for (QName qname : props)
|
|
{
|
|
result[count++] = useShortQNames ? getShortQName(qname).toString() : qname.toString();
|
|
}
|
|
return Context.getCurrentContext().newArray(this.scope, result);
|
|
}
|
|
|
|
/**
|
|
* @return true if this Node is a container (i.e. a folder)
|
|
*/
|
|
public boolean getIsContainer()
|
|
{
|
|
if (isContainer == null)
|
|
{
|
|
DictionaryService dd = this.services.getDictionaryService();
|
|
isContainer = Boolean.valueOf((dd.isSubClass(getQNameType(), ContentModel.TYPE_FOLDER) == true &&
|
|
dd.isSubClass(getQNameType(), ContentModel.TYPE_SYSTEM_FOLDER) == false));
|
|
}
|
|
|
|
return isContainer.booleanValue();
|
|
}
|
|
|
|
/**
|
|
* @return true if this Node is a Document (i.e. with content)
|
|
*/
|
|
public boolean getIsDocument()
|
|
{
|
|
if (isDocument == null)
|
|
{
|
|
DictionaryService dd = this.services.getDictionaryService();
|
|
isDocument = Boolean.valueOf(dd.isSubClass(getQNameType(), ContentModel.TYPE_CONTENT));
|
|
}
|
|
|
|
return isDocument.booleanValue();
|
|
}
|
|
|
|
/**
|
|
* @return true if this Node is a Link to a Container (i.e. a folderlink)
|
|
*/
|
|
public boolean getIsLinkToContainer()
|
|
{
|
|
if (isLinkToContainer == null)
|
|
{
|
|
DictionaryService dd = this.services.getDictionaryService();
|
|
isLinkToContainer = Boolean.valueOf(dd.isSubClass(getQNameType(), ApplicationModel.TYPE_FOLDERLINK));
|
|
}
|
|
|
|
return isLinkToContainer.booleanValue();
|
|
}
|
|
|
|
/**
|
|
* @return true if this Node is a Link to a Document (i.e. a filelink)
|
|
*/
|
|
public boolean getIsLinkToDocument()
|
|
{
|
|
if (isLinkToDocument == null)
|
|
{
|
|
DictionaryService dd = this.services.getDictionaryService();
|
|
isLinkToDocument = Boolean.valueOf(dd.isSubClass(getQNameType(), ApplicationModel.TYPE_FILELINK));
|
|
}
|
|
|
|
return isLinkToDocument.booleanValue();
|
|
}
|
|
|
|
/**
|
|
* @return true if the Node is a Category
|
|
*/
|
|
public boolean getIsCategory()
|
|
{
|
|
// this valid is overriden by the CategoryNode sub-class
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @return The list of aspects applied to this node
|
|
*/
|
|
public Set<QName> getAspectsSet()
|
|
{
|
|
if (this.aspects == null)
|
|
{
|
|
this.aspects = this.nodeService.getAspects(this.nodeRef);
|
|
}
|
|
|
|
return this.aspects;
|
|
}
|
|
|
|
/**
|
|
* @return The array of aspects applied to this node
|
|
*/
|
|
public Scriptable getAspects()
|
|
{
|
|
Set<QName> aspects = getAspectsSet();
|
|
Object[] result = new Object[aspects.size()];
|
|
int count = 0;
|
|
for (QName qname : aspects)
|
|
{
|
|
result[count++] = qname.toString();
|
|
}
|
|
return Context.getCurrentContext().newArray(this.scope, result);
|
|
}
|
|
|
|
/**
|
|
* @param aspect The aspect name to test for (fully qualified or short-name form)
|
|
* @return true if the node has the aspect false otherwise
|
|
*/
|
|
public boolean hasAspect(String aspect)
|
|
{
|
|
return getAspectsSet().contains(createQName(aspect));
|
|
}
|
|
|
|
/**
|
|
* @param type The qname type to test this object against (fully qualified or short-name form)
|
|
* @return true if this Node is a sub-type of the specified class (or itself of that class)
|
|
*/
|
|
public boolean isSubType(String type)
|
|
{
|
|
ParameterCheck.mandatoryString("Type", type);
|
|
|
|
QName qnameType = createQName(type);
|
|
|
|
return this.services.getDictionaryService().isSubClass(getQNameType(), qnameType);
|
|
}
|
|
|
|
/**
|
|
* @return QName path to this node. This can be used for Lucene PATH: style queries
|
|
*/
|
|
public String getQnamePath()
|
|
{
|
|
return this.services.getNodeService().getPath(getNodeRef()).toPrefixString(this.services.getNamespaceService());
|
|
}
|
|
|
|
/**
|
|
* @return Display path to this node
|
|
*/
|
|
public String getDisplayPath()
|
|
{
|
|
if (displayPath == null)
|
|
{
|
|
displayPath = this.nodeService.getPath(this.nodeRef).toDisplayPath(
|
|
this.nodeService, this.services.getPermissionService());
|
|
}
|
|
|
|
return displayPath;
|
|
}
|
|
|
|
/**
|
|
* @return the small icon image for this node
|
|
*/
|
|
public String getIcon16()
|
|
{
|
|
return "/images/filetypes/_default.gif";
|
|
}
|
|
|
|
/**
|
|
* @return the large icon image for this node
|
|
*/
|
|
public String getIcon32()
|
|
{
|
|
return "/images/filetypes32/_default.gif";
|
|
}
|
|
|
|
/**
|
|
* @return true if the node is currently locked
|
|
*/
|
|
public boolean getIsLocked()
|
|
{
|
|
boolean locked = false;
|
|
|
|
if (getAspectsSet().contains(ContentModel.ASPECT_LOCKABLE))
|
|
{
|
|
LockStatus lockStatus = this.services.getLockService().getLockStatus(this.nodeRef);
|
|
if (lockStatus == LockStatus.LOCKED || lockStatus == LockStatus.LOCK_OWNER)
|
|
{
|
|
locked = true;
|
|
}
|
|
}
|
|
|
|
return locked;
|
|
}
|
|
|
|
/**
|
|
* @return the primary parent node
|
|
*/
|
|
public ScriptNode getParent()
|
|
{
|
|
if (parent == null)
|
|
{
|
|
NodeRef parentRef = getPrimaryParentAssoc().getParentRef();
|
|
// handle root node (no parent!)
|
|
if (parentRef != null)
|
|
{
|
|
parent = newInstance(parentRef, this.services, this.scope);
|
|
}
|
|
}
|
|
|
|
return parent;
|
|
}
|
|
|
|
/**
|
|
* @return all parent nodes
|
|
*/
|
|
public Scriptable getParents()
|
|
{
|
|
List<ChildAssociationRef> parentRefs = this.nodeService.getParentAssocs(this.nodeRef);
|
|
Object[] parents = new Object[parentRefs.size()];
|
|
for (int i = 0; i < parentRefs.size(); i++)
|
|
{
|
|
NodeRef ref = parentRefs.get(i).getParentRef();
|
|
parents[i] = newInstance(ref, this.services, this.scope);
|
|
}
|
|
return Context.getCurrentContext().newArray(this.scope, parents);
|
|
}
|
|
|
|
/**
|
|
* @return the primary parent association so we can get at the association QName and the association type QName.
|
|
*/
|
|
public ChildAssociationRef getPrimaryParentAssoc()
|
|
{
|
|
if (primaryParentAssoc == null)
|
|
{
|
|
primaryParentAssoc = this.nodeService.getPrimaryParent(nodeRef);
|
|
}
|
|
return primaryParentAssoc;
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------------------
|
|
// Content API
|
|
|
|
/**
|
|
* @return the content String for this node from the default content property (@see ContentModel.PROP_CONTENT)
|
|
*/
|
|
public String getContent()
|
|
{
|
|
String content = "";
|
|
|
|
ScriptContentData contentData = (ScriptContentData)getProperties().get(ContentModel.PROP_CONTENT);
|
|
if (contentData != null)
|
|
{
|
|
content = contentData.getContent();
|
|
}
|
|
|
|
return content;
|
|
}
|
|
|
|
/**
|
|
* Set the content for this node
|
|
*
|
|
* @param content Content string to set
|
|
*/
|
|
public void setContent(String content)
|
|
{
|
|
ScriptContentData contentData = (ScriptContentData)getProperties().get(ContentModel.PROP_CONTENT);
|
|
if (contentData != null)
|
|
{
|
|
contentData.setContent(content);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return For a content document, this method returns the URL to the content stream for the default content
|
|
* property (@see ContentModel.PROP_CONTENT)
|
|
* <p>
|
|
* For a container node, this method return the URL to browse to the folder in the web-client
|
|
*/
|
|
public String getUrl()
|
|
{
|
|
if (getIsDocument() == true)
|
|
{
|
|
return MessageFormat.format(CONTENT_DEFAULT_URL, new Object[] { nodeRef.getStoreRef().getProtocol(),
|
|
nodeRef.getStoreRef().getIdentifier(), nodeRef.getId(),
|
|
URLEncoder.encode(getName())});
|
|
}
|
|
else
|
|
{
|
|
return MessageFormat.format(FOLDER_BROWSE_URL, new Object[] { nodeRef.getStoreRef().getProtocol(),
|
|
nodeRef.getStoreRef().getIdentifier(), nodeRef.getId() });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return For a content document, this method returns the download URL to the content for
|
|
* the default content property (@see ContentModel.PROP_CONTENT)
|
|
* <p>
|
|
* For a container node, this method returns an empty string
|
|
*/
|
|
public String getDownloadUrl()
|
|
{
|
|
if (getIsDocument() == true)
|
|
{
|
|
return MessageFormat.format(CONTENT_DOWNLOAD_URL, new Object[] {
|
|
nodeRef.getStoreRef().getProtocol(),
|
|
nodeRef.getStoreRef().getIdentifier(),
|
|
nodeRef.getId(),
|
|
URLEncoder.encode(getName()) });
|
|
}
|
|
else
|
|
{
|
|
return "";
|
|
}
|
|
}
|
|
|
|
public String jsGet_downloadUrl()
|
|
{
|
|
return getDownloadUrl();
|
|
}
|
|
|
|
/**
|
|
* @return The WebDav cm:name based path to the content for the default content property
|
|
* (@see ContentModel.PROP_CONTENT)
|
|
*/
|
|
public String getWebdavUrl()
|
|
{
|
|
String url = "";
|
|
try
|
|
{
|
|
if (getIsContainer() || getIsDocument())
|
|
{
|
|
List<FileInfo> paths = this.services.getFileFolderService().getNamePath(null, getNodeRef());
|
|
|
|
// build up the webdav url
|
|
StringBuilder path = new StringBuilder(128);
|
|
path.append("/webdav");
|
|
|
|
// build up the path skipping the first path as it is the root folder
|
|
for (int i=1; i<paths.size(); i++)
|
|
{
|
|
path.append("/")
|
|
.append(URLEncoder.encode(paths.get(i).getName()));
|
|
}
|
|
url = path.toString();
|
|
}
|
|
}
|
|
catch (InvalidTypeException typeErr)
|
|
{
|
|
// cannot build path if file is a type such as a rendition
|
|
}
|
|
catch (FileNotFoundException nodeErr)
|
|
{
|
|
// cannot build path if file no longer exists
|
|
}
|
|
return url;
|
|
}
|
|
|
|
/**
|
|
* @return The mimetype encoding for content attached to the node from the default content property
|
|
* (@see ContentModel.PROP_CONTENT)
|
|
*/
|
|
public String getMimetype()
|
|
{
|
|
String mimetype = null;
|
|
ScriptContentData content = (ScriptContentData) this.getProperties().get(ContentModel.PROP_CONTENT);
|
|
if (content != null)
|
|
{
|
|
mimetype = content.getMimetype();
|
|
}
|
|
|
|
return mimetype;
|
|
}
|
|
|
|
/**
|
|
* Set the mimetype encoding for the content attached to the node from the default content property
|
|
* (@see ContentModel.PROP_CONTENT)
|
|
*
|
|
* @param mimetype Mimetype to set
|
|
*/
|
|
public void setMimetype(String mimetype)
|
|
{
|
|
ScriptContentData content = (ScriptContentData) this.getProperties().get(ContentModel.PROP_CONTENT);
|
|
if (content != null)
|
|
{
|
|
content.setMimetype(mimetype);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return The size in bytes of the content attached to the node from the default content property
|
|
* (@see ContentModel.PROP_CONTENT)
|
|
*/
|
|
public long getSize()
|
|
{
|
|
long size = 0;
|
|
ScriptContentData content = (ScriptContentData) this.getProperties().get(ContentModel.PROP_CONTENT);
|
|
if (content != null)
|
|
{
|
|
size = content.getSize();
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------------------
|
|
// Security API
|
|
|
|
/**
|
|
* Return true if the user has the specified permission on the node.
|
|
* <p>
|
|
* The default permissions are found in <code>org.alfresco.service.cmr.security.PermissionService</code>.
|
|
* Most commonly used are "Write", "Delete" and "AddChildren".
|
|
*
|
|
* @param permission as found in <code>org.alfresco.service.cmr.security.PermissionService</code>
|
|
* @return true if the user has the specified permission on the node.
|
|
*/
|
|
public boolean hasPermission(String permission)
|
|
{
|
|
ParameterCheck.mandatory("Permission Name", permission);
|
|
|
|
boolean allowed = false;
|
|
|
|
if (permission != null && permission.length() != 0)
|
|
{
|
|
AccessStatus status = this.services.getPermissionService().hasPermission(this.nodeRef, permission);
|
|
allowed = (AccessStatus.ALLOWED == status);
|
|
}
|
|
|
|
return allowed;
|
|
}
|
|
|
|
/**
|
|
* @return Array of permissions applied to this Node, including inherited.
|
|
* Strings returned are of the format [ALLOWED|DENIED];[USERNAME|GROUPNAME];PERMISSION for example
|
|
* ALLOWED;kevinr;Consumer so can be easily tokenized on the ';' character.
|
|
*/
|
|
public Scriptable getPermissions()
|
|
{
|
|
return Context.getCurrentContext().newArray(this.scope, retrieveAllSetPermissions(false, false));
|
|
}
|
|
|
|
/**
|
|
* @return Array of permissions applied directly to this Node (does not include inherited).
|
|
* Strings returned are of the format [ALLOWED|DENIED];[USERNAME|GROUPNAME];PERMISSION for example
|
|
* ALLOWED;kevinr;Consumer so can be easily tokenized on the ';' character.
|
|
*/
|
|
public Scriptable getDirectPermissions()
|
|
{
|
|
return Context.getCurrentContext().newArray(this.scope, retrieveAllSetPermissions(true, false));
|
|
}
|
|
|
|
/**
|
|
* @return Array of all permissions applied to this Node, including inherited.
|
|
* Strings returned are of the format [ALLOWED|DENIED];[USERNAME|GROUPNAME];PERMISSION;[INHERITED|DIRECT]
|
|
* for example: ALLOWED;kevinr;Consumer;DIRECT so can be easily tokenized on the ';' character.
|
|
*/
|
|
public Scriptable getFullPermissions()
|
|
{
|
|
return Context.getCurrentContext().newArray(this.scope, retrieveAllSetPermissions(false, true));
|
|
}
|
|
|
|
/**
|
|
* Helper to construct the response object for the various getPermissions() calls.
|
|
*
|
|
* @param direct True to only retrieve direct permissions, false to get inherited also
|
|
* @param full True to retrieve full data string with [INHERITED|DIRECT] element
|
|
* This exists to maintain backward compatibility with existing permission APIs.
|
|
*
|
|
* @return Object[] of packed permission strings.
|
|
*/
|
|
protected Object[] retrieveAllSetPermissions(boolean direct, boolean full)
|
|
{
|
|
Set<AccessPermission> acls = this.services.getPermissionService().getAllSetPermissions(getNodeRef());
|
|
List<Object> permissions = new ArrayList<Object>(acls.size());
|
|
for (AccessPermission permission : acls)
|
|
{
|
|
if (!direct || permission.isSetDirectly())
|
|
{
|
|
StringBuilder buf = new StringBuilder(64);
|
|
buf.append(permission.getAccessStatus())
|
|
.append(';')
|
|
.append(permission.getAuthority())
|
|
.append(';')
|
|
.append(permission.getPermission());
|
|
if (full)
|
|
{
|
|
buf.append(';').append(permission.isSetDirectly() ? "DIRECT" : "INHERITED");
|
|
}
|
|
permissions.add(buf.toString());
|
|
}
|
|
}
|
|
return (Object[])permissions.toArray(new Object[permissions.size()]);
|
|
}
|
|
|
|
/**
|
|
* @return Array of settable permissions for this Node
|
|
*/
|
|
public Scriptable getSettablePermissions()
|
|
{
|
|
Set<String> permissions = this.services.getPermissionService().getSettablePermissions(getNodeRef());
|
|
Object[] result = permissions.toArray(new Object[0]);
|
|
return Context.getCurrentContext().newArray(this.scope, result);
|
|
}
|
|
|
|
/**
|
|
* @return true if the node inherits permissions from the parent node, false otherwise
|
|
*/
|
|
public boolean inheritsPermissions()
|
|
{
|
|
return this.services.getPermissionService().getInheritParentPermissions(this.nodeRef);
|
|
}
|
|
|
|
/**
|
|
* Set whether this node should inherit permissions from the parent node.
|
|
*
|
|
* @param inherit True to inherit parent permissions, false otherwise.
|
|
*/
|
|
public void setInheritsPermissions(boolean inherit)
|
|
{
|
|
this.services.getPermissionService().setInheritParentPermissions(this.nodeRef, inherit);
|
|
}
|
|
|
|
/**
|
|
* Apply a permission for ALL users to the node.
|
|
*
|
|
* @param permission Permission to apply
|
|
* @see org.alfresco.service.cmr.security.PermissionService
|
|
*/
|
|
public void setPermission(String permission)
|
|
{
|
|
ParameterCheck.mandatoryString("Permission Name", permission);
|
|
this.services.getPermissionService().setPermission(
|
|
this.nodeRef, PermissionService.ALL_AUTHORITIES, permission, true);
|
|
}
|
|
|
|
/**
|
|
* Apply a permission for the specified authority (e.g. username or group) to the node.
|
|
*
|
|
* @param permission Permission to apply @see org.alfresco.service.cmr.security.PermissionService
|
|
* @param authority Authority (generally a username or group name) to apply the permission for
|
|
*/
|
|
public void setPermission(String permission, String authority)
|
|
{
|
|
ParameterCheck.mandatoryString("Permission Name", permission);
|
|
ParameterCheck.mandatoryString("Authority", authority);
|
|
this.services.getPermissionService().setPermission(
|
|
this.nodeRef, authority, permission, true);
|
|
}
|
|
|
|
/**
|
|
* Remove a permission for ALL user from the node.
|
|
*
|
|
* @param permission Permission to remove @see org.alfresco.service.cmr.security.PermissionService
|
|
*/
|
|
public void removePermission(String permission)
|
|
{
|
|
ParameterCheck.mandatoryString("Permission Name", permission);
|
|
this.services.getPermissionService().deletePermission(
|
|
this.nodeRef, PermissionService.ALL_AUTHORITIES, permission);
|
|
}
|
|
|
|
/**
|
|
* Remove a permission for the specified authority (e.g. username or group) from the node.
|
|
*
|
|
* @param permission Permission to remove @see org.alfresco.service.cmr.security.PermissionService
|
|
* @param authority Authority (generally a username or group name) to apply the permission for
|
|
*/
|
|
public void removePermission(String permission, String authority)
|
|
{
|
|
ParameterCheck.mandatoryString("Permission Name", permission);
|
|
ParameterCheck.mandatoryString("Authority", authority);
|
|
this.services.getPermissionService().deletePermission(
|
|
this.nodeRef, authority, permission);
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------------------
|
|
// Ownership API
|
|
|
|
/**
|
|
* Set the owner of the node
|
|
*/
|
|
public void setOwner(String userId)
|
|
{
|
|
this.services.getOwnableService().setOwner(this.nodeRef, userId);
|
|
}
|
|
|
|
/**
|
|
* Take ownership of the node.
|
|
*/
|
|
public void takeOwnership()
|
|
{
|
|
this.services.getOwnableService().takeOwnership(this.nodeRef);
|
|
}
|
|
|
|
/**
|
|
* Get the owner of the node.
|
|
*
|
|
* @return
|
|
*/
|
|
public String getOwner()
|
|
{
|
|
return this.services.getOwnableService().getOwner(this.nodeRef);
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------------------
|
|
// Create and Modify API
|
|
|
|
/**
|
|
* Persist the modified properties of this Node.
|
|
*/
|
|
public void save()
|
|
{
|
|
// persist properties back to the node in the DB
|
|
Map<QName, Serializable> props = new HashMap<QName, Serializable>(getProperties().size());
|
|
for (String key : this.properties.keySet())
|
|
{
|
|
Serializable value = (Serializable) this.properties.get(key);
|
|
|
|
// perform the conversion from script wrapper object to repo serializable values
|
|
value = getValueConverter().convertValueForRepo(value);
|
|
|
|
props.put(createQName(key), value);
|
|
}
|
|
this.nodeService.setProperties(this.nodeRef, props);
|
|
}
|
|
|
|
/**
|
|
* Re-sets the type of the node. Can be called in order specialise a node to a sub-type. This should be used
|
|
* with caution since calling it changes the type of the node and thus* implies a different set of aspects,
|
|
* properties and associations. It is the responsibility of the caller to ensure that the node is in a
|
|
* approriate state after changing the type.
|
|
*
|
|
* @param type Type to specialize the node
|
|
*
|
|
* @return true if successful, false otherwise
|
|
*/
|
|
public boolean specializeType(String type)
|
|
{
|
|
ParameterCheck.mandatoryString("Type", type);
|
|
|
|
QName qnameType = createQName(type);
|
|
|
|
// Ensure that we are performing a specialise
|
|
if (getQNameType().equals(qnameType) == false &&
|
|
this.services.getDictionaryService().isSubClass(qnameType, getQNameType()) == true)
|
|
{
|
|
// Specialise the type of the node
|
|
this.nodeService.setType(this.nodeRef, qnameType);
|
|
this.type = qnameType;
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Create a new File (cm:content) node as a child of this node.
|
|
* <p>
|
|
* Once created the file should have content set using the <code>content</code> property.
|
|
*
|
|
* Beware: Any unsaved property changes will be lost when this is called. To preserve property changes call {@link save()} first.
|
|
*
|
|
* @param name Name of the file to create
|
|
*
|
|
* @return Newly created Node or null if failed to create.
|
|
*/
|
|
public ScriptNode createFile(String name)
|
|
{
|
|
return createFile(name, null);
|
|
}
|
|
|
|
/**
|
|
* Create a new File (cm:content) node as a child of this node.
|
|
* <p>
|
|
* Once created the file should have content set using the <code>content</code> property.
|
|
*
|
|
* Beware: Any unsaved property changes will be lost when this is called. To preserve property changes call {@link save()} first.
|
|
*
|
|
* @param name Name of the file to create
|
|
* @param type Type of the file to create (if null, defaults to ContentModel.TYPE_CONTENT)
|
|
*
|
|
* @return Newly created Node or null if failed to create.
|
|
*/
|
|
public ScriptNode createFile(String name, String type)
|
|
{
|
|
ParameterCheck.mandatoryString("Node Name", name);
|
|
|
|
FileInfo fileInfo = this.services.getFileFolderService().create(
|
|
this.nodeRef, name, type == null ? ContentModel.TYPE_CONTENT : createQName(type));
|
|
|
|
reset();
|
|
|
|
ScriptNode file = newInstance(fileInfo.getNodeRef(), this.services, this.scope);
|
|
file.setMimetype(this.services.getMimetypeService().guessMimetype(name));
|
|
|
|
return file;
|
|
}
|
|
|
|
/**
|
|
* Create a new folder (cm:folder) node as a child of this node.
|
|
*
|
|
* Beware: Any unsaved property changes will be lost when this is called. To preserve property changes call {@link save()} first.
|
|
*
|
|
* @param name Name of the folder to create
|
|
*
|
|
* @return Newly created Node or null if failed to create.
|
|
*/
|
|
public ScriptNode createFolder(String name)
|
|
{
|
|
return createFolder(name, null);
|
|
}
|
|
|
|
/**
|
|
* Create a new folder (cm:folder) node as a child of this node.
|
|
*
|
|
* Beware: Any unsaved property changes will be lost when this is called. To preserve property changes call {@link save()} first.
|
|
*
|
|
* @param name Name of the folder to create
|
|
* @param type Type of the folder to create (if null, defaults to ContentModel.TYPE_FOLDER)
|
|
*
|
|
* @return Newly created Node or null if failed to create.
|
|
*/
|
|
public ScriptNode createFolder(String name, String type)
|
|
{
|
|
ParameterCheck.mandatoryString("Node Name", name);
|
|
|
|
FileInfo fileInfo = this.services.getFileFolderService().create(
|
|
this.nodeRef, name, type == null ? ContentModel.TYPE_FOLDER : createQName(type));
|
|
|
|
reset();
|
|
|
|
return newInstance(fileInfo.getNodeRef(), this.services, this.scope);
|
|
}
|
|
|
|
/**
|
|
* Create a new Node of the specified type as a child of this node.
|
|
*
|
|
* @param name Name of the node to create (can be null for a node without a 'cm:name' property)
|
|
* @param type QName type (fully qualified or short form such as 'cm:content')
|
|
*
|
|
* @return Newly created Node or null if failed to create.
|
|
*/
|
|
public ScriptNode createNode(String name, String type)
|
|
{
|
|
return createNode(name, type, null, ContentModel.ASSOC_CONTAINS.toString());
|
|
}
|
|
|
|
/**
|
|
* Create a new Node of the specified type as a child of this node.
|
|
*
|
|
* @param name Name of the node to create (can be null for a node without a 'cm:name' property)
|
|
* @param type QName type (fully qualified or short form such as 'cm:content')
|
|
* @param assocType QName of the child association type (fully qualified or short form e.g. 'cm:contains')
|
|
*
|
|
* @return Newly created Node or null if failed to create.
|
|
*/
|
|
public ScriptNode createNode(String name, String type, String assocType)
|
|
{
|
|
return createNode(name, type, null, assocType);
|
|
}
|
|
|
|
/**
|
|
* Create a new Node of the specified type as a child of this node.
|
|
*
|
|
* @param name Name of the node to create (can be null for a node without a 'cm:name' property)
|
|
* @param type QName type (fully qualified or short form such as 'cm:content')
|
|
* @param properties Associative array of the default properties for the node.
|
|
*
|
|
* @return Newly created Node or null if failed to create.
|
|
*/
|
|
public ScriptNode createNode(String name, String type, Object properties)
|
|
{
|
|
return createNode(name, type, properties, ContentModel.ASSOC_CONTAINS.toString());
|
|
}
|
|
|
|
/**
|
|
* Create a new Node of the specified type as a child of this node.
|
|
*
|
|
* Beware: Any unsaved property changes will be lost when this is called. To preserve property changes call {@link save()} first.
|
|
*
|
|
* @param name Name of the node to create (can be null for a node without a 'cm:name' property)
|
|
* @param type QName type (fully qualified or short form such as 'cm:content')
|
|
* @param properties Associative array of the default properties for the node.
|
|
* @param assocType QName of the child association type (fully qualified or short form e.g. 'cm:contains')
|
|
*
|
|
* @return Newly created Node or null if failed to create.
|
|
*/
|
|
public ScriptNode createNode(String name, String type, Object properties, String assocType)
|
|
{
|
|
return createNode(name, type, properties, assocType, null);
|
|
}
|
|
|
|
/**
|
|
* Create a new Node of the specified type as a child of this node.
|
|
*
|
|
* Beware: Any unsaved property changes will be lost when this is called. To preserve property changes call {@link save()} first.
|
|
*
|
|
* @param name Name of the node to create (can be null for a node without a 'cm:name' property)
|
|
* @param type QName type (fully qualified or short form such as 'cm:content')
|
|
* @param properties Associative array of the default properties for the node.
|
|
* @param assocType QName of the child association type (fully qualified or short form e.g. 'cm:contains')
|
|
* @param assocName QName of the child association name (fully qualified or short form e.g. 'fm:discussion')
|
|
*
|
|
* @return Newly created Node or null if failed to create.
|
|
*/
|
|
public ScriptNode createNode(String name, String type, Object properties, String assocType, String assocName)
|
|
{
|
|
ParameterCheck.mandatoryString("Node Type", type);
|
|
ParameterCheck.mandatoryString("Association Type", assocType);
|
|
|
|
Map<QName, Serializable> props = null;
|
|
|
|
if (properties instanceof ScriptableObject)
|
|
{
|
|
props = new HashMap<QName, Serializable>(4, 1.0f);
|
|
extractScriptableProperties((ScriptableObject)properties, props);
|
|
}
|
|
|
|
if (name != null)
|
|
{
|
|
if (props == null) props = new HashMap<QName, Serializable>(1, 1.0f);
|
|
props.put(ContentModel.PROP_NAME, name);
|
|
}
|
|
else
|
|
{
|
|
// set name for the assoc local name
|
|
name = GUID.generate();
|
|
}
|
|
|
|
ChildAssociationRef childAssocRef = this.nodeService.createNode(
|
|
this.nodeRef,
|
|
createQName(assocType),
|
|
assocName == null ?
|
|
QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, QName.createValidLocalName(name)) :
|
|
createQName(assocName),
|
|
createQName(type),
|
|
props);
|
|
|
|
reset();
|
|
|
|
return newInstance(childAssocRef.getChildRef(), this.services, this.scope);
|
|
}
|
|
|
|
/**
|
|
* Creates a new secondary association between the current node and the specified child node.
|
|
* The association is given the same name as the child node's primary association.
|
|
*
|
|
* Beware: Any unsaved property changes will be lost when this is called. To preserve property changes call {@link save()} first.
|
|
*
|
|
* @param node node to add as a child of this node
|
|
*/
|
|
public void addNode(ScriptNode node)
|
|
{
|
|
ParameterCheck.mandatory("node", node);
|
|
ChildAssociationRef childAssocRef = this.nodeService.getPrimaryParent(node.nodeRef);
|
|
nodeService.addChild(this.nodeRef, node.nodeRef, ContentModel.ASSOC_CONTAINS, childAssocRef.getQName());
|
|
reset();
|
|
}
|
|
|
|
/**
|
|
* Remove an existing child node of this node.
|
|
*
|
|
* Severs all parent-child relationships between two nodes.
|
|
* <p>
|
|
* The child node will be cascade deleted if one of the associations was the
|
|
* primary association, i.e. the one with which the child node was created.
|
|
*
|
|
* Beware: Any unsaved property changes will be lost when this is called. To preserve property changes call {@link save()} first.
|
|
*
|
|
* @param node child node to remove
|
|
*/
|
|
public void removeNode(ScriptNode node)
|
|
{
|
|
ParameterCheck.mandatory("node", node);
|
|
nodeService.removeChild(this.nodeRef, node.nodeRef);
|
|
reset();
|
|
}
|
|
|
|
/**
|
|
* Create an association between this node and the specified target node.
|
|
*
|
|
* Beware: Any unsaved property changes will be lost when this is called. To preserve property changes call {@link save()} first.
|
|
*
|
|
* @param target Destination node for the association
|
|
* @param assocType Association type qname (short form or fully qualified)
|
|
*/
|
|
public Association createAssociation(ScriptNode target, String assocType)
|
|
{
|
|
ParameterCheck.mandatory("Target", target);
|
|
ParameterCheck.mandatoryString("Association Type Name", assocType);
|
|
|
|
AssociationRef assocRef = this.nodeService.createAssociation(this.nodeRef, target.nodeRef, createQName(assocType));
|
|
reset();
|
|
return new Association(this.services, assocRef);
|
|
}
|
|
|
|
/**
|
|
* Remove an association between this node and the specified target node.
|
|
*
|
|
* Beware: Any unsaved property changes will be lost when this is called. To preserve property changes call {@link save()} first.
|
|
*
|
|
* @param target Destination node on the end of the association
|
|
* @param assocType Association type qname (short form or fully qualified)
|
|
*/
|
|
public void removeAssociation(ScriptNode target, String assocType)
|
|
{
|
|
ParameterCheck.mandatory("Target", target);
|
|
ParameterCheck.mandatoryString("Association Type Name", assocType);
|
|
|
|
this.nodeService.removeAssociation(this.nodeRef, target.nodeRef, createQName(assocType));
|
|
reset();
|
|
}
|
|
|
|
/**
|
|
* Remove this node. Any references to this Node or its NodeRef should be discarded!
|
|
*
|
|
* Beware: Any unsaved property changes will be lost when this is called. To preserve property changes call {@link save()} first.
|
|
*
|
|
*/
|
|
public boolean remove()
|
|
{
|
|
boolean success = false;
|
|
|
|
if (nodeService.exists(this.nodeRef))
|
|
{
|
|
this.nodeService.deleteNode(this.nodeRef);
|
|
success = true;
|
|
}
|
|
|
|
reset();
|
|
|
|
return success;
|
|
}
|
|
|
|
/**
|
|
* Copy this Node to a new parent destination. Note that children of the source Node are not copied.
|
|
*
|
|
* @param destination Node
|
|
*
|
|
* @return The newly copied Node instance or null if failed to copy.
|
|
*/
|
|
public ScriptNode copy(ScriptNode destination)
|
|
{
|
|
ScriptNode copy = copy(destination, false);
|
|
|
|
// ALF-9517 fix
|
|
if (copy != null && copy.hasAspect(ContentModel.ASPECT_VERSIONABLE.toString()))
|
|
{
|
|
copy.ensureVersioningEnabled(true, true);
|
|
}
|
|
return copy;
|
|
}
|
|
|
|
/**
|
|
* Copy this Node and potentially all child nodes to a new parent destination.
|
|
*
|
|
* @param destination Node
|
|
* @param deepCopy True for a deep copy, false otherwise.
|
|
*
|
|
* @return The newly copied Node instance or null if failed to copy.
|
|
*/
|
|
public ScriptNode copy(ScriptNode destination, boolean deepCopy)
|
|
{
|
|
ParameterCheck.mandatory("Destination Node", destination);
|
|
|
|
ScriptNode copy = null;
|
|
|
|
if (destination.getNodeRef().getStoreRef().getProtocol().equals(StoreRef.PROTOCOL_WORKSPACE))
|
|
{
|
|
NodeRef copyRef = this.services.getCopyService().copyAndRename(this.nodeRef, destination.getNodeRef(),
|
|
ContentModel.ASSOC_CONTAINS, null, deepCopy);
|
|
copy = newInstance(copyRef, this.services, this.scope);
|
|
}
|
|
else
|
|
{
|
|
// NOTE: the deepCopy flag is not respected for this copy mechanism
|
|
copy = getCrossRepositoryCopyHelper().copy(this, destination, getName());
|
|
}
|
|
|
|
return copy;
|
|
}
|
|
|
|
/**
|
|
* Revert this Node to the specified version. Note this is not a deep revert of
|
|
* associations.
|
|
* This node must have the cm:versionable aspect. It will be checked out if required
|
|
* but will be checked in after the call.
|
|
*
|
|
* @param versionLabel to revert from
|
|
*
|
|
* @return the original Node that was checked out if reverted, {@code null} otherwise
|
|
* (if the version does not exist).
|
|
*/
|
|
public ScriptNode revert(String history, boolean majorVersion, String versionLabel)
|
|
{
|
|
return revert(history, majorVersion, versionLabel, false);
|
|
}
|
|
|
|
/**
|
|
* Revert this Node to the specified version and potentially all child nodes.
|
|
* This node must have the cm:versionable aspect. It will be checked out if required
|
|
* but will be checked in after the call.
|
|
*
|
|
* Beware: Any unsaved property changes will be lost when this is called. To preserve property changes call {@link save()} first.
|
|
*
|
|
* @param history Version history note
|
|
* @param majorVersion True to save as a major version increment, false for minor version.
|
|
* @param versionLabel to revert from
|
|
* @param deep {@code true} for a deep revert, {@code false} otherwise.
|
|
*
|
|
* @return the original Node that was checked out if reverted, {@code null} otherwise
|
|
* (if the version does not exist).
|
|
*/
|
|
public ScriptNode revert(String history, boolean majorVersion, String versionLabel, boolean deep)
|
|
{
|
|
if (!getIsVersioned())
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// Get the Version - needed to do the revert
|
|
Version version = services.getVersionService().getVersionHistory(nodeRef).getVersion(versionLabel);
|
|
if (version == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// Checkout the node if required
|
|
ScriptNode workingCopy = this;
|
|
if (!nodeService.hasAspect(nodeRef, ContentModel.ASPECT_WORKING_COPY))
|
|
{
|
|
workingCopy = checkout();
|
|
}
|
|
|
|
// Checkin the node - to get the new version
|
|
workingCopy = workingCopy.checkin(history, majorVersion);
|
|
|
|
// Revert the new (current) version of the node
|
|
services.getVersionService().revert(workingCopy.nodeRef, version, deep);
|
|
|
|
return workingCopy;
|
|
}
|
|
|
|
/**
|
|
* Move this Node to a new parent destination.
|
|
*
|
|
* Beware: Any unsaved property changes will be lost when this is called. To preserve property changes call {@link save()} first.
|
|
*
|
|
* @param destination Node
|
|
*
|
|
* @return true on successful move, false on failure to move.
|
|
*/
|
|
public boolean move(ScriptNode destination)
|
|
{
|
|
ParameterCheck.mandatory("Destination Node", destination);
|
|
|
|
this.primaryParentAssoc = this.nodeService.moveNode(this.nodeRef, destination.getNodeRef(),
|
|
ContentModel.ASSOC_CONTAINS, getPrimaryParentAssoc().getQName());
|
|
|
|
// reset cached values
|
|
reset();
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Move this Node from specified parent to a new parent destination.
|
|
*
|
|
* Beware: Any unsaved property changes will be lost when this is called. To preserve property changes call {@link save()} first.
|
|
*
|
|
* @param source Node
|
|
* @param destination Node
|
|
* @return true on successful move, false on failure to move.
|
|
*/
|
|
public boolean move(ScriptNode source, ScriptNode destination)
|
|
{
|
|
ParameterCheck.mandatory("Destination Node", destination);
|
|
|
|
if (source == null)
|
|
{
|
|
return move(destination);
|
|
}
|
|
else
|
|
{
|
|
try
|
|
{
|
|
this.services.getFileFolderService().moveFrom(this.nodeRef, source.getNodeRef(), destination.getNodeRef(), null);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
throw new ScriptException("Can't move node", e);
|
|
}
|
|
}
|
|
|
|
// reset cached values
|
|
reset();
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Add an aspect to the Node. As no properties are provided in this call, it can only be used to add aspects that do not require any mandatory properties.
|
|
*
|
|
* Beware: Any unsaved property changes will be lost when this is called. To preserve property changes call {@link save()} first.
|
|
* @param type Type name of the aspect to add
|
|
*
|
|
* @return true if the aspect was added successfully, false if an error occured.
|
|
*/
|
|
public boolean addAspect(String type)
|
|
{
|
|
return addAspect(type, null);
|
|
}
|
|
|
|
/**
|
|
* Add an aspect to the Node.
|
|
*
|
|
* Beware: Any unsaved property changes will be lost when this is called. To preserve property changes call {@link save()} first.
|
|
*
|
|
* @param type Type name of the aspect to add
|
|
* @param props ScriptableObject (generally an assocative array) providing the named properties for the aspect
|
|
* - any mandatory properties for the aspect must be provided!
|
|
*
|
|
* @return true if the aspect was added successfully, false if an error occured.
|
|
*/
|
|
public boolean addAspect(String type, Object props)
|
|
{
|
|
ParameterCheck.mandatoryString("Aspect Type", type);
|
|
|
|
Map<QName, Serializable> aspectProps = null;
|
|
if (props instanceof ScriptableObject)
|
|
{
|
|
aspectProps = new HashMap<QName, Serializable>(4, 1.0f);
|
|
extractScriptableProperties((ScriptableObject)props, aspectProps);
|
|
}
|
|
QName aspectQName = createQName(type);
|
|
if (aspectQName.equals(ContentModel.ASPECT_VERSIONABLE))
|
|
{
|
|
ensureVersioningEnabled(true, true);
|
|
}
|
|
else
|
|
{
|
|
this.nodeService.addAspect(this.nodeRef, aspectQName, aspectProps);
|
|
}
|
|
|
|
// reset the relevant cached node members
|
|
reset();
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Extract a map of properties from a scriptable object (generally an associative array)
|
|
*
|
|
* @param scriptable The scriptable object to extract name/value pairs from.
|
|
* @param map The map to add the converted name/value pairs to.
|
|
*/
|
|
private void extractScriptableProperties(ScriptableObject scriptable, Map<QName, Serializable> map)
|
|
{
|
|
// we need to get all the keys to the properties provided
|
|
// and convert them to a Map of QName to Serializable objects
|
|
Object[] propIds = scriptable.getIds();
|
|
for (int i = 0; i < propIds.length; i++)
|
|
{
|
|
// work on each key in turn
|
|
Object propId = propIds[i];
|
|
|
|
// we are only interested in keys that are formed of Strings i.e. QName.toString()
|
|
if (propId instanceof String)
|
|
{
|
|
// get the value out for the specified key - it must be Serializable
|
|
String key = (String)propId;
|
|
Object value = scriptable.get(key, scriptable);
|
|
if (value instanceof Serializable)
|
|
{
|
|
value = getValueConverter().convertValueForRepo((Serializable)value);
|
|
map.put(createQName(key), (Serializable)value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove aspect from the node.
|
|
*
|
|
* Beware: Any unsaved property changes will be lost when this is called. To preserve property changes call {@link save()} first.
|
|
*
|
|
* @param type the aspect type
|
|
*
|
|
* @return true if successful, false otherwise
|
|
*/
|
|
public boolean removeAspect(String type)
|
|
{
|
|
ParameterCheck.mandatoryString("Aspect Type", type);
|
|
|
|
QName aspectQName = createQName(type);
|
|
this.nodeService.removeAspect(this.nodeRef, aspectQName);
|
|
|
|
// reset the relevant cached node members
|
|
reset();
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------------------
|
|
// Checkout/Checkin Services
|
|
|
|
/**
|
|
* Ensures that this document has the cm:versionable aspect applied to it,
|
|
* and that it has the initial version in the version store.
|
|
* Calling this on a versioned node with a version store entry will have
|
|
* no effect.
|
|
* Calling this on a newly uploaded share node will have versioning enabled
|
|
* for it (Share currently does lazy versioning to improve performance of
|
|
* documents that are uploaded but never edited, and multi upload performance).
|
|
*
|
|
* @param autoVersion If the cm:versionable aspect is applied, should auto versioning be requested?
|
|
* @param autoVersionProps If the cm:versionable aspect is applied, should auto versioning of properties be requested?
|
|
*/
|
|
public void ensureVersioningEnabled(boolean autoVersion, boolean autoVersionProps)
|
|
{
|
|
Map<QName, Serializable> props = new HashMap<QName, Serializable>(1, 1.0f);
|
|
props.put(ContentModel.PROP_AUTO_VERSION, autoVersion);
|
|
props.put(ContentModel.PROP_AUTO_VERSION_PROPS, autoVersionProps);
|
|
|
|
this.services.getVersionService().ensureVersioningEnabled(nodeRef, props);
|
|
}
|
|
|
|
/**
|
|
* Create a version of this document. Note: this will add the cm:versionable aspect.
|
|
*
|
|
* @param history Version history note
|
|
* @param majorVersion True to save as a major version increment, false for minor version.
|
|
*
|
|
* @return ScriptVersion object representing the newly added version node
|
|
*/
|
|
public ScriptVersion createVersion(String history, boolean majorVersion)
|
|
{
|
|
Map<String, Serializable> props = new HashMap<String, Serializable>(2, 1.0f);
|
|
props.put(Version.PROP_DESCRIPTION, history);
|
|
props.put(VersionModel.PROP_VERSION_TYPE, majorVersion ? VersionType.MAJOR : VersionType.MINOR);
|
|
ScriptVersion version = new ScriptVersion(this.services.getVersionService().createVersion(this.nodeRef, props), this.services, this.scope);
|
|
this.versions = null;
|
|
return version;
|
|
}
|
|
|
|
/**
|
|
* Determines if this node is versioned
|
|
*
|
|
* @return true => is versioned
|
|
*/
|
|
public boolean getIsVersioned()
|
|
{
|
|
return this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE);
|
|
}
|
|
|
|
/**
|
|
* Gets the version history
|
|
*
|
|
* @return version history
|
|
*/
|
|
public Scriptable getVersionHistory()
|
|
{
|
|
if (this.versions == null && getIsVersioned())
|
|
{
|
|
VersionHistory history = this.services.getVersionService().getVersionHistory(this.nodeRef);
|
|
if (history != null)
|
|
{
|
|
Collection<Version> allVersions = history.getAllVersions();
|
|
Object[] versions = new Object[allVersions.size()];
|
|
int i = 0;
|
|
for (Version version : allVersions)
|
|
{
|
|
versions[i++] = new ScriptVersion(version, this.services, this.scope);
|
|
}
|
|
this.versions = Context.getCurrentContext().newArray(this.scope, versions);
|
|
}
|
|
}
|
|
return this.versions;
|
|
}
|
|
|
|
/**
|
|
* Gets the version of this node specified by version label
|
|
*
|
|
* @param versionLabel version label
|
|
* @return version of node, or null if node is not versioned, or label does not exist
|
|
*/
|
|
public ScriptVersion getVersion(String versionLabel)
|
|
{
|
|
if (!getIsVersioned())
|
|
{
|
|
return null;
|
|
}
|
|
VersionHistory history = this.services.getVersionService().getVersionHistory(this.nodeRef);
|
|
Version version = history.getVersion(versionLabel);
|
|
if (version == null)
|
|
{
|
|
return null;
|
|
}
|
|
return new ScriptVersion(version, this.services, this.scope);
|
|
}
|
|
|
|
/**
|
|
* Perform a check-out of this document into the current parent space.
|
|
*
|
|
* @return the working copy Node for the checked out document
|
|
*/
|
|
public ScriptNode checkout()
|
|
{
|
|
NodeRef workingCopyRef = this.services.getCheckOutCheckInService().checkout(this.nodeRef);
|
|
ScriptNode workingCopy = newInstance(workingCopyRef, this.services, this.scope);
|
|
|
|
// reset the aspect and properties as checking out a document causes changes
|
|
this.properties = null;
|
|
this.aspects = null;
|
|
|
|
return workingCopy;
|
|
}
|
|
|
|
/**
|
|
* Performs a check-out of this document for the purposes of an upload
|
|
*
|
|
* @return
|
|
*/
|
|
public ScriptNode checkoutForUpload()
|
|
{
|
|
AlfrescoTransactionSupport.bindResource("checkoutforupload", Boolean.TRUE.toString());
|
|
services.getRuleService().disableRules();
|
|
try
|
|
{
|
|
return checkout();
|
|
}
|
|
finally
|
|
{
|
|
services.getRuleService().enableRules();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Perform a check-out of this document into the specified destination space.
|
|
*
|
|
* @param destination
|
|
* Destination for the checked out document working copy Node.
|
|
* @return the working copy Node for the checked out document
|
|
*/
|
|
public ScriptNode checkout(ScriptNode destination)
|
|
{
|
|
ParameterCheck.mandatory("Destination Node", destination);
|
|
|
|
ChildAssociationRef childAssocRef = this.nodeService.getPrimaryParent(destination.getNodeRef());
|
|
NodeRef workingCopyRef = this.services.getCheckOutCheckInService().checkout(this.nodeRef,
|
|
destination.getNodeRef(), ContentModel.ASSOC_CONTAINS, childAssocRef.getQName());
|
|
ScriptNode workingCopy = newInstance(workingCopyRef, this.services, this.scope);
|
|
|
|
// reset the aspect and properties as checking out a document causes changes
|
|
this.properties = null;
|
|
this.aspects = null;
|
|
|
|
return workingCopy;
|
|
}
|
|
|
|
/**
|
|
* Check-in a working copy document. The current state of the working copy is copied to the original node,
|
|
* this will include any content updated in the working node. Note that this method can only be called on a
|
|
* working copy Node.
|
|
*
|
|
* @return the original Node that was checked out.
|
|
*/
|
|
public ScriptNode checkin()
|
|
{
|
|
return checkin("", false);
|
|
}
|
|
|
|
/**
|
|
* Check-in a working copy document. The current state of the working copy is copied to the original node,
|
|
* this will include any content updated in the working node. Note that this method can only be called on a
|
|
* working copy Node.
|
|
*
|
|
* @param history Version history note
|
|
*
|
|
* @return the original Node that was checked out.
|
|
*/
|
|
public ScriptNode checkin(String history)
|
|
{
|
|
return checkin(history, false);
|
|
}
|
|
|
|
/**
|
|
* Check-in a working copy document. The current state of the working copy is copied to the original node,
|
|
* this will include any content updated in the working node. Note that this method can only be called on a
|
|
* working copy Node.
|
|
*
|
|
* @param history Version history note
|
|
* @param majorVersion True to save as a major version increment, false for minor version.
|
|
*
|
|
* @return the original Node that was checked out.
|
|
*/
|
|
public ScriptNode checkin(String history, boolean majorVersion)
|
|
{
|
|
Map<String, Serializable> props = new HashMap<String, Serializable>(2, 1.0f);
|
|
props.put(Version.PROP_DESCRIPTION, history);
|
|
props.put(VersionModel.PROP_VERSION_TYPE, majorVersion ? VersionType.MAJOR : VersionType.MINOR);
|
|
NodeRef original = this.services.getCheckOutCheckInService().checkin(this.nodeRef, props);
|
|
this.versions = null;
|
|
return newInstance(original, this.services, this.scope);
|
|
}
|
|
|
|
/**
|
|
* Cancel the check-out of a working copy document. The working copy will be deleted and any changes made to it
|
|
* are lost. Note that this method can only be called on a working copy Node. The reference to this working copy
|
|
* Node should be discarded.
|
|
*
|
|
* @return the original Node that was checked out.
|
|
*/
|
|
public ScriptNode cancelCheckout()
|
|
{
|
|
NodeRef original = this.services.getCheckOutCheckInService().cancelCheckout(this.nodeRef);
|
|
return newInstance(original, this.services, this.scope);
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------
|
|
// Transformation and Rendering API
|
|
|
|
/**
|
|
* Transform a document to a new document mimetype format. A copy of the document is made and the extension
|
|
* changed to match the new mimetype, then the transformation isapplied.
|
|
*
|
|
* @param mimetype Mimetype destination for the transformation
|
|
*
|
|
* @return Node representing the newly transformed document.
|
|
*/
|
|
public ScriptNode transformDocument(String mimetype)
|
|
{
|
|
return transformDocument(mimetype, getPrimaryParentAssoc().getParentRef());
|
|
}
|
|
|
|
/**
|
|
* Transform a document to a new document mimetype format. A copy of the document is made in the specified
|
|
* destination folder and the extension changed to match the new mimetype, then then transformation is applied.
|
|
*
|
|
* @param mimetype Mimetype destination for the transformation
|
|
* @param destination Destination folder location
|
|
*
|
|
* @return Node representing the newly transformed document.
|
|
*/
|
|
public ScriptNode transformDocument(String mimetype, ScriptNode destination)
|
|
{
|
|
return transformDocument(mimetype, destination.getNodeRef());
|
|
}
|
|
|
|
private ScriptNode transformDocument(String mimetype, NodeRef destination)
|
|
{
|
|
ParameterCheck.mandatoryString("Mimetype", mimetype);
|
|
ParameterCheck.mandatory("Destination Node", destination);
|
|
final NodeRef sourceNodeRef = nodeRef;
|
|
|
|
// the delegate definition for transforming a document
|
|
Transformer transformer = new Transformer()
|
|
{
|
|
public ScriptNode transform(ContentService contentService, NodeRef nodeRef, ContentReader reader,
|
|
ContentWriter writer)
|
|
{
|
|
ScriptNode transformedNode = null;
|
|
TransformationOptions options = new TransformationOptions();
|
|
options.setSourceNodeRef(sourceNodeRef);
|
|
|
|
if (contentService.isTransformable(reader, writer, options))
|
|
{
|
|
contentService.transform(reader, writer, options);
|
|
transformedNode = newInstance(nodeRef, services, scope);
|
|
}
|
|
return transformedNode;
|
|
}
|
|
};
|
|
|
|
return transformNode(transformer, mimetype, destination);
|
|
}
|
|
|
|
/**
|
|
* Generic method to transform Node content from one mimetype to another.
|
|
*
|
|
* @param transformer The Transformer delegate supplying the transformation logic
|
|
* @param mimetype Mimetype of the destination content
|
|
* @param destination Destination folder location for the resulting document
|
|
*
|
|
* @return Node representing the transformed content - or null if the transform failed
|
|
*/
|
|
private ScriptNode transformNode(Transformer transformer, String mimetype, NodeRef destination)
|
|
{
|
|
ScriptNode transformedNode = null;
|
|
|
|
// get the content reader
|
|
ContentService contentService = this.services.getContentService();
|
|
ContentReader reader = contentService.getReader(this.nodeRef, ContentModel.PROP_CONTENT);
|
|
|
|
// only perform the transformation if some content is available
|
|
if (reader != null)
|
|
{
|
|
// Copy the content node to a new node
|
|
String copyName = TransformActionExecuter.transformName(this.services.getMimetypeService(), getName(),
|
|
mimetype, true);
|
|
NodeRef copyNodeRef = this.services.getCopyService().copy(this.nodeRef, destination,
|
|
ContentModel.ASSOC_CONTAINS,
|
|
QName.createQName(ContentModel.PROP_CONTENT.getNamespaceURI(), QName.createValidLocalName(copyName)),
|
|
false);
|
|
|
|
// modify the name of the copy to reflect the new mimetype
|
|
this.nodeService.setProperty(copyNodeRef, ContentModel.PROP_NAME, copyName);
|
|
|
|
// get the writer and set it up
|
|
ContentWriter writer = contentService.getWriter(copyNodeRef, ContentModel.PROP_CONTENT, true);
|
|
writer.setMimetype(mimetype); // new mimetype
|
|
writer.setEncoding(reader.getEncoding()); // original encoding
|
|
|
|
// Try and transform the content using the supplied delegate
|
|
transformedNode = transformer.transform(contentService, copyNodeRef, reader, writer);
|
|
}
|
|
|
|
return transformedNode;
|
|
}
|
|
|
|
/**
|
|
* Transform an image to a new image format. A copy of the image document is made and the extension changed to
|
|
* match the new mimetype, then the transformation is applied.
|
|
*
|
|
* @param mimetype Mimetype destination for the transformation
|
|
*
|
|
* @return Node representing the newly transformed image.
|
|
*/
|
|
public ScriptNode transformImage(String mimetype)
|
|
{
|
|
return transformImage(mimetype, null, getPrimaryParentAssoc().getParentRef());
|
|
}
|
|
|
|
/**
|
|
* Transform an image to a new image format. A copy of the image document is made and the extension changed to
|
|
* match the new mimetype, then the transformation is applied.
|
|
*
|
|
* @param mimetype Mimetype destination for the transformation
|
|
* @param options Image convert command options
|
|
*
|
|
* @return Node representing the newly transformed image.
|
|
*/
|
|
public ScriptNode transformImage(String mimetype, String options)
|
|
{
|
|
return transformImage(mimetype, options, getPrimaryParentAssoc().getParentRef());
|
|
}
|
|
|
|
/**
|
|
* Transform an image to a new image mimetype format. A copy of the image document is made in the specified
|
|
* destination folder and the extension changed to match the newmimetype, then then transformation is applied.
|
|
*
|
|
* @param mimetype Mimetype destination for the transformation
|
|
* @param destination Destination folder location
|
|
*
|
|
* @return Node representing the newly transformed image.
|
|
*/
|
|
public ScriptNode transformImage(String mimetype, ScriptNode destination)
|
|
{
|
|
ParameterCheck.mandatory("Destination Node", destination);
|
|
return transformImage(mimetype, null, destination.getNodeRef());
|
|
}
|
|
|
|
/**
|
|
* Transform an image to a new image mimetype format. A copy of the image document is made in the specified
|
|
* destination folder and the extension changed to match the new
|
|
* mimetype, then then transformation is applied.
|
|
*
|
|
* @param mimetype Mimetype destination for the transformation
|
|
* @param options Image convert command options
|
|
* @param destination Destination folder location
|
|
*
|
|
* @return Node representing the newly transformed image.
|
|
*/
|
|
public ScriptNode transformImage(String mimetype, String options, ScriptNode destination)
|
|
{
|
|
ParameterCheck.mandatory("Destination Node", destination);
|
|
return transformImage(mimetype, options, destination.getNodeRef());
|
|
}
|
|
|
|
private ScriptNode transformImage(String mimetype, final String options, NodeRef destination)
|
|
{
|
|
ParameterCheck.mandatoryString("Mimetype", mimetype);
|
|
final NodeRef sourceNodeRef = nodeRef;
|
|
|
|
// the delegate definition for transforming an image
|
|
Transformer transformer = new Transformer()
|
|
{
|
|
public ScriptNode transform(ContentService contentService, NodeRef nodeRef, ContentReader reader,
|
|
ContentWriter writer)
|
|
{
|
|
ImageTransformationOptions imageOptions = new ImageTransformationOptions();
|
|
imageOptions.setSourceNodeRef(sourceNodeRef);
|
|
|
|
if (options != null)
|
|
{
|
|
imageOptions.setCommandOptions(options);
|
|
}
|
|
contentService.getImageTransformer().transform(reader, writer, imageOptions);
|
|
|
|
return newInstance(nodeRef, services, scope);
|
|
}
|
|
};
|
|
|
|
return transformNode(transformer, mimetype, destination);
|
|
}
|
|
|
|
/**
|
|
* Process a FreeMarker Template against the current node.
|
|
*
|
|
* @param template Node of the template to execute
|
|
*
|
|
* @return output of the template execution
|
|
*/
|
|
public String processTemplate(ScriptNode template)
|
|
{
|
|
ParameterCheck.mandatory("Template Node", template);
|
|
return processTemplate(template.getContent(), null, null);
|
|
}
|
|
|
|
/**
|
|
* Process a FreeMarker Template against the current node.
|
|
*
|
|
* @param template Node of the template to execute
|
|
* @param args Scriptable object (generally an associative array) containing the name/value pairs of
|
|
* arguments to be passed to the template
|
|
*
|
|
* @return output of the template execution
|
|
*/
|
|
public String processTemplate(ScriptNode template, Object args)
|
|
{
|
|
ParameterCheck.mandatory("Template Node", template);
|
|
return processTemplate(template.getContent(), null, (ScriptableObject)args);
|
|
}
|
|
|
|
/**
|
|
* Process a FreeMarker Template against the current node.
|
|
*
|
|
* @param template The template to execute
|
|
*
|
|
* @return output of the template execution
|
|
*/
|
|
public String processTemplate(String template)
|
|
{
|
|
ParameterCheck.mandatoryString("Template", template);
|
|
return processTemplate(template, null, null);
|
|
}
|
|
|
|
/**
|
|
* Process a FreeMarker Template against the current node.
|
|
*
|
|
* @param template The template to execute
|
|
* @param args Scriptable object (generally an associative array) containing the name/value pairs of
|
|
* arguments to be passed to the template
|
|
*
|
|
* @return output of the template execution
|
|
*/
|
|
public String processTemplate(String template, Object args)
|
|
{
|
|
ParameterCheck.mandatoryString("Template", template);
|
|
return processTemplate(template, null, (ScriptableObject)args);
|
|
}
|
|
|
|
private String processTemplate(String template, NodeRef templateRef, ScriptableObject args)
|
|
{
|
|
Object person = (Object)scope.get("person", scope);
|
|
Object companyhome = (Object)scope.get("companyhome", scope);
|
|
Object userhome = (Object)scope.get("userhome", scope);
|
|
|
|
// build default model for the template processing
|
|
Map<String, Object> model = this.services.getTemplateService().buildDefaultModel(
|
|
(person.equals(UniqueTag.NOT_FOUND)) ? null : ((ScriptNode)((Wrapper)person).unwrap()).getNodeRef(),
|
|
(companyhome.equals(UniqueTag.NOT_FOUND)) ? null : ((ScriptNode)((Wrapper)companyhome).unwrap()).getNodeRef(),
|
|
(userhome.equals(UniqueTag.NOT_FOUND)) ? null : ((ScriptNode)((Wrapper)userhome).unwrap()).getNodeRef(),
|
|
templateRef,
|
|
null);
|
|
|
|
// add the current node as either the document/space as appropriate
|
|
if (this.getIsDocument())
|
|
{
|
|
model.put("document", this.nodeRef);
|
|
model.put("space", getPrimaryParentAssoc().getParentRef());
|
|
}
|
|
else
|
|
{
|
|
model.put("space", this.nodeRef);
|
|
}
|
|
|
|
// add the supplied args to the 'args' root object
|
|
if (args != null)
|
|
{
|
|
// we need to get all the keys to the properties provided
|
|
// and convert them to a Map of QName to Serializable objects
|
|
Object[] propIds = args.getIds();
|
|
Map<String, String> templateArgs = new HashMap<String, String>(propIds.length);
|
|
for (int i = 0; i < propIds.length; i++)
|
|
{
|
|
// work on each key in turn
|
|
Object propId = propIds[i];
|
|
|
|
// we are only interested in keys that are formed of Strings i.e. QName.toString()
|
|
if (propId instanceof String)
|
|
{
|
|
// get the value out for the specified key - make sure it is Serializable
|
|
Object value = args.get((String) propId, args);
|
|
value = getValueConverter().convertValueForRepo((Serializable)value);
|
|
if (value != null)
|
|
{
|
|
templateArgs.put((String) propId, value.toString());
|
|
}
|
|
}
|
|
}
|
|
// add the args to the model as the 'args' root object
|
|
model.put("args", templateArgs);
|
|
}
|
|
|
|
// execute template!
|
|
// TODO: check that script modified nodes are reflected...
|
|
return this.services.getTemplateService().processTemplateString(null, template, model);
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------
|
|
// Thumbnail Methods
|
|
|
|
/**
|
|
* Creates a thumbnail for the content property of the node.
|
|
*
|
|
* The thumbnail name correspionds to pre-set thumbnail details stored in the
|
|
* repository.
|
|
*
|
|
* @param thumbnailName the name of the thumbnail
|
|
* @return ScriptThumbnail the newly create thumbnail node
|
|
*/
|
|
public ScriptThumbnail createThumbnail(String thumbnailName)
|
|
{
|
|
return createThumbnail(thumbnailName, false);
|
|
}
|
|
|
|
/**
|
|
* Creates a thumbnail for the content property of the node.
|
|
*
|
|
* The thumbnail name correspionds to pre-set thumbnail details stored in the
|
|
* repository.
|
|
*
|
|
* If the thumbnail is created asynchronously then the result will be null and creation
|
|
* of the thumbnail will occure at some point in the background.
|
|
*
|
|
* @param thumbnailName the name of the thumbnail
|
|
* @param async indicates whether the thumbnail is create asynchronously or not
|
|
* @return ScriptThumbnail the newly create thumbnail node or null if async creation occures
|
|
*/
|
|
public ScriptThumbnail createThumbnail(String thumbnailName, boolean async)
|
|
{
|
|
ScriptThumbnail result = null;
|
|
|
|
// Use the thumbnail registy to get the details of the thumbail
|
|
ThumbnailRegistry registry = this.services.getThumbnailService().getThumbnailRegistry();
|
|
ThumbnailDefinition details = registry.getThumbnailDefinition(thumbnailName);
|
|
if (details == null)
|
|
{
|
|
// Throw exception
|
|
throw new ScriptException("The thumbnail name '" + thumbnailName + "' is not registered");
|
|
}
|
|
|
|
// If there's nothing currently registered to generate thumbnails for the
|
|
// specified mimetype, then log a message and bail out
|
|
String nodeMimeType = getMimetype();
|
|
Serializable value = this.nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT);
|
|
ContentData contentData = DefaultTypeConverter.INSTANCE.convert(ContentData.class, value);
|
|
if (contentData == null)
|
|
{
|
|
logger.info("Unable to create thumbnail '" + details.getName() + "' as there is no content");
|
|
return null;
|
|
}
|
|
if (!registry.isThumbnailDefinitionAvailable(contentData.getContentUrl(), nodeMimeType, getSize(), nodeRef, details))
|
|
{
|
|
logger.info("Unable to create thumbnail '" + details.getName() + "' for " +
|
|
nodeMimeType + " as no transformer is currently available");
|
|
return null;
|
|
}
|
|
|
|
// Have the thumbnail created
|
|
if (async == false)
|
|
{
|
|
// Create the thumbnail
|
|
NodeRef thumbnailNodeRef = this.services.getThumbnailService().createThumbnail(
|
|
this.nodeRef,
|
|
ContentModel.PROP_CONTENT,
|
|
details.getMimetype(),
|
|
details.getTransformationOptions(),
|
|
details.getName());
|
|
|
|
// Create the thumbnail script object
|
|
result = new ScriptThumbnail(thumbnailNodeRef, this.services, this.scope);
|
|
}
|
|
else
|
|
{
|
|
Action action = ThumbnailHelper.createCreateThumbnailAction(details, services);
|
|
|
|
// Queue async creation of thumbnail
|
|
this.services.getActionService().executeAction(action, this.nodeRef, true, true);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Get the given thumbnail for the content property
|
|
*
|
|
* @param thumbnailName the thumbnail name
|
|
* @return ScriptThumbnail the thumbnail
|
|
*/
|
|
public ScriptThumbnail getThumbnail(String thumbnailName)
|
|
{
|
|
ScriptThumbnail result = null;
|
|
NodeRef thumbnailNodeRef = this.services.getThumbnailService().getThumbnailByName(
|
|
this.nodeRef,
|
|
ContentModel.PROP_CONTENT,
|
|
thumbnailName);
|
|
if (thumbnailNodeRef != null)
|
|
{
|
|
result = new ScriptThumbnail(thumbnailNodeRef, this.services, this.scope);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Get the all the thumbnails for a given node's content property.
|
|
*
|
|
* @return Scriptable list of thumbnails, empty if none available
|
|
*/
|
|
public ScriptThumbnail[] getThumbnails()
|
|
{
|
|
List<NodeRef> thumbnails = this.services.getThumbnailService().getThumbnails(
|
|
this.nodeRef,
|
|
ContentModel.PROP_CONTENT,
|
|
null,
|
|
null);
|
|
|
|
List<ScriptThumbnail> result = new ArrayList<ScriptThumbnail>(thumbnails.size());
|
|
for (NodeRef thumbnail : thumbnails)
|
|
{
|
|
ScriptThumbnail scriptThumbnail = new ScriptThumbnail(thumbnail, this.services, this.scope);
|
|
result.add(scriptThumbnail);
|
|
}
|
|
return (ScriptThumbnail[])result.toArray(new ScriptThumbnail[result.size()]);
|
|
}
|
|
|
|
/**
|
|
* Returns the names of the thumbnail defintions that can be applied to the content property of
|
|
* this node.
|
|
* <p>
|
|
* Thumbanil defintions only appear in this list if they can produce a thumbnail for the content
|
|
* found in the content property. This will be determined by looking at the mimetype of the content
|
|
* and the destinatino mimetype of the thumbnail.
|
|
*
|
|
* @return String[] array of thumbnail names that are valid for the current content type
|
|
*/
|
|
public String[] getThumbnailDefintions()
|
|
{
|
|
ContentService contentService = this.services.getContentService();
|
|
ThumbnailService thumbnailService = this.services.getThumbnailService();
|
|
|
|
List<String> result = new ArrayList<String>(7);
|
|
|
|
ContentReader contentReader = contentService.getReader(this.nodeRef, ContentModel.PROP_CONTENT);
|
|
if (contentReader != null)
|
|
{
|
|
String mimetype = contentReader.getMimetype();
|
|
List<ThumbnailDefinition> thumbnailDefinitions = thumbnailService.getThumbnailRegistry().getThumbnailDefinitions(mimetype, contentReader.getSize());
|
|
for (ThumbnailDefinition thumbnailDefinition : thumbnailDefinitions)
|
|
{
|
|
result.add(thumbnailDefinition.getName());
|
|
}
|
|
}
|
|
|
|
return (String[])result.toArray(new String[result.size()]);
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------------------
|
|
// Tag methods
|
|
|
|
/**
|
|
* Clear the node's tags
|
|
*/
|
|
public void clearTags()
|
|
{
|
|
this.services.getTaggingService().clearTags(this.nodeRef);
|
|
updateTagProperty();
|
|
}
|
|
|
|
/**
|
|
* Adds a tag to the node
|
|
*
|
|
* @param tag tag name
|
|
*/
|
|
public void addTag(String tag)
|
|
{
|
|
this.services.getTaggingService().addTag(this.nodeRef, tag);
|
|
updateTagProperty();
|
|
}
|
|
|
|
/**
|
|
* Adds all the tags to the node
|
|
*
|
|
* @param tags array of tag names
|
|
*/
|
|
public void addTags(String[] tags)
|
|
{
|
|
this.services.getTaggingService().addTags(this.nodeRef, Arrays.asList(tags));
|
|
updateTagProperty();
|
|
}
|
|
|
|
/**
|
|
* Removes a tag from the node
|
|
*
|
|
* @param tag tag name
|
|
*/
|
|
public void removeTag(String tag)
|
|
{
|
|
this.services.getTaggingService().removeTag(this.nodeRef, tag);
|
|
updateTagProperty();
|
|
}
|
|
|
|
/**
|
|
* Removes all the tags from the node
|
|
*
|
|
* @param tags array of tag names
|
|
*/
|
|
public void removeTags(String[] tags)
|
|
{
|
|
this.services.getTaggingService().removeTags(this.nodeRef, Arrays.asList(tags));
|
|
updateTagProperty();
|
|
}
|
|
|
|
/**
|
|
* Get all the tags applied to this node
|
|
*
|
|
* @return String[] array containing all the tag applied to this node
|
|
*/
|
|
public String[] getTags()
|
|
{
|
|
String[] result = null;
|
|
List<String> tags = this.services.getTaggingService().getTags(this.nodeRef);
|
|
if (tags.isEmpty() == true)
|
|
{
|
|
result = new String[0];
|
|
}
|
|
else
|
|
{
|
|
result = (String[])tags.toArray(new String[tags.size()]);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Set the tags applied to this node. This overwirtes the list of tags currently applied to the
|
|
* node.
|
|
*
|
|
* @param tags array of tags
|
|
*/
|
|
public void setTags(String[] tags)
|
|
{
|
|
this.services.getTaggingService().setTags(this.nodeRef, Arrays.asList(tags));
|
|
updateTagProperty();
|
|
}
|
|
|
|
private void updateTagProperty()
|
|
{
|
|
Serializable tags = this.services.getNodeService().getProperty(this.nodeRef, ContentModel.PROP_TAGS);
|
|
if (this.properties != null)
|
|
{
|
|
this.properties.put(ContentModel.PROP_TAGS.toString(), getValueConverter().convertValueForScript(ContentModel.PROP_TAGS, tags));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets whether this node is a tag scope or not
|
|
*
|
|
* @param value true if this node is a tag scope, false otherwise
|
|
*/
|
|
public void setIsTagScope(boolean value)
|
|
{
|
|
boolean currentValue = this.services.getTaggingService().isTagScope(this.nodeRef);
|
|
if (currentValue != value)
|
|
{
|
|
if (value == true)
|
|
{
|
|
// Add the tag scope
|
|
this.services.getTaggingService().addTagScope(this.nodeRef);
|
|
}
|
|
else
|
|
{
|
|
// Remove the tag scope
|
|
this.services.getTaggingService().removeTagScope(this.nodeRef);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets whether this node is a tag scope
|
|
*
|
|
* @return boolean true if this node is a tag scope, false otherwise
|
|
*/
|
|
public boolean getIsTagScope()
|
|
{
|
|
return this.services.getTaggingService().isTagScope(this.nodeRef);
|
|
}
|
|
|
|
/**
|
|
* Gets the 'nearest' tag scope to this node by travesing up the parent hierarchy untill one is found.
|
|
* <p>
|
|
* If none is found, null is returned.
|
|
*
|
|
* @return TagScope the 'nearest' tag scope
|
|
*/
|
|
public TagScope getTagScope()
|
|
{
|
|
TagScope tagScope = null;
|
|
org.alfresco.service.cmr.tagging.TagScope tagScopeImpl = this.services.getTaggingService().findTagScope(this.nodeRef);
|
|
if (tagScopeImpl != null)
|
|
{
|
|
tagScope = new TagScope(this.services.getTaggingService(), tagScopeImpl);
|
|
}
|
|
return tagScope;
|
|
}
|
|
|
|
/**
|
|
* Gets all (deep) children of this node that have the tag specified.
|
|
*
|
|
* @param tag tag name
|
|
* @return ScriptNode[] nodes that are deep children of the node with the tag
|
|
*/
|
|
public ScriptNode[] childrenByTags(String tag)
|
|
{
|
|
List<NodeRef> nodeRefs = this.services.getTaggingService().findTaggedNodes(this.nodeRef.getStoreRef(), tag, this.nodeRef);
|
|
ScriptNode[] nodes = new ScriptNode[nodeRefs.size()];
|
|
int index = 0;
|
|
for (NodeRef node : nodeRefs)
|
|
{
|
|
nodes[index] = new ScriptNode(node, this.services, this.scope);
|
|
index ++;
|
|
}
|
|
return nodes;
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------------------
|
|
// Workflow methods
|
|
|
|
/**
|
|
* Get active workflow instances this node belongs to
|
|
*
|
|
* @return the active workflow instances this node belongs to
|
|
*/
|
|
public Scriptable getActiveWorkflows()
|
|
{
|
|
if (this.activeWorkflows == null)
|
|
{
|
|
WorkflowService workflowService = this.services.getWorkflowService();
|
|
|
|
List<WorkflowInstance> workflowInstances = workflowService.getWorkflowsForContent(this.nodeRef, true);
|
|
Object[] jsWorkflowInstances = new Object[workflowInstances.size()];
|
|
int index = 0;
|
|
for (WorkflowInstance workflowInstance : workflowInstances)
|
|
{
|
|
jsWorkflowInstances[index++] = new JscriptWorkflowInstance(workflowInstance, this.services, this.scope);
|
|
}
|
|
this.activeWorkflows = Context.getCurrentContext().newArray(this.scope, jsWorkflowInstances);
|
|
}
|
|
|
|
return this.activeWorkflows;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------
|
|
// Site methods
|
|
|
|
/**
|
|
* Returns the short name of the site this node is located within. If the
|
|
* node is not located within a site null is returned.
|
|
*
|
|
* @return The short name of the site this node is located within, null
|
|
* if the node is not located within a site.
|
|
*/
|
|
public String getSiteShortName()
|
|
{
|
|
if (!this.siteNameResolved)
|
|
{
|
|
this.siteNameResolved = true;
|
|
|
|
Path path = this.services.getNodeService().getPath(getNodeRef());
|
|
|
|
if (logger.isDebugEnabled())
|
|
logger.debug("Determing if node is within a site using path: " + path);
|
|
|
|
for (int i = 0; i < path.size(); i++)
|
|
{
|
|
if ("st:sites".equals(path.get(i).getPrefixedString(this.services.getNamespaceService())))
|
|
{
|
|
// we now know the node is in a site, find the next element in the array (if there is one)
|
|
if ((i+1) < path.size())
|
|
{
|
|
// get the site name
|
|
Path.Element siteName = path.get(i+1);
|
|
|
|
// remove the "cm:" prefix and add to result object
|
|
this.siteName = ISO9075.decode(siteName.getPrefixedString(
|
|
this.services.getNamespaceService()).substring(3));
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
logger.debug(this.siteName != null ?
|
|
"Node is in the site named \"" + this.siteName + "\"" : "Node is not in a site");
|
|
}
|
|
|
|
return this.siteName;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------
|
|
// Helper methods
|
|
|
|
/**
|
|
* Override Object.toString() to provide useful debug output
|
|
*/
|
|
public String toString()
|
|
{
|
|
if (this.nodeService.exists(nodeRef))
|
|
{
|
|
if (this.services.getPermissionService().hasPermission(nodeRef, PermissionService.READ_PROPERTIES) == AccessStatus.ALLOWED)
|
|
{
|
|
// TODO: DC: Allow debug output of property values - for now it's disabled as this could potentially
|
|
// follow a large network of nodes. Unfortunately, JBPM issues unprotected debug statements
|
|
// where node.toString is used - will request this is fixed in next release of JBPM.
|
|
return "Node Type: " + getType() + ", Node Aspects: " + getAspectsSet().toString();
|
|
}
|
|
else
|
|
{
|
|
return "Access denied to node " + nodeRef;
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
return "Node no longer exists: " + nodeRef;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the JSON representation of this node.
|
|
*
|
|
* @param useShortQNames if true short-form qnames will be returned, else long-form.
|
|
* @return The JSON representation of this node
|
|
*/
|
|
public String toJSON(boolean useShortQNames)
|
|
{
|
|
// This method is used by the /api/metadata web script
|
|
String jsonStr = "{}";
|
|
|
|
if (this.nodeService.exists(nodeRef))
|
|
{
|
|
if(this.services.getPublicServiceAccessService().hasAccess(ServiceRegistry.NODE_SERVICE.getLocalName(), "getProperties", this.nodeRef) == AccessStatus.ALLOWED)
|
|
{
|
|
JSONObject json = new JSONObject();
|
|
|
|
try
|
|
{
|
|
// add type info
|
|
json.put("nodeRef", this.getNodeRef().toString());
|
|
|
|
String typeString = useShortQNames ? getShortQName(this.getQNameType())
|
|
: this.getType();
|
|
json.put("type", typeString);
|
|
json.put("mimetype", this.getMimetype());
|
|
|
|
// Fetch all properties
|
|
Map<QName, Serializable> nodeProperties = this.nodeService.getProperties(this.nodeRef);
|
|
|
|
// Do any special conversion steps that are needed
|
|
for (QName longQName : nodeProperties.keySet())
|
|
{
|
|
Serializable value = nodeProperties.get(longQName);
|
|
|
|
if (value instanceof Date)
|
|
{
|
|
value = ISO8601DateFormat.format((Date)value);
|
|
nodeProperties.put(longQName, value);
|
|
}
|
|
if (value instanceof NodeRef)
|
|
{
|
|
value = ((NodeRef)value).toString();
|
|
nodeProperties.put(longQName, value);
|
|
}
|
|
}
|
|
|
|
if (useShortQNames)
|
|
{
|
|
Map<String, Serializable> nodePropertiesShortQNames
|
|
= new LinkedHashMap<String, Serializable>(nodeProperties.size());
|
|
for (QName nextLongQName : nodeProperties.keySet())
|
|
{
|
|
try
|
|
{
|
|
String nextShortQName = getShortQName(nextLongQName);
|
|
nodePropertiesShortQNames.put(nextShortQName, nodeProperties.get(nextLongQName));
|
|
}
|
|
catch (NamespaceException ne)
|
|
{
|
|
// ignore properties that do not have a registered namespace
|
|
|
|
if (logger.isDebugEnabled())
|
|
logger.debug("Ignoring property '" + nextLongQName + "' as it's namespace is not registered");
|
|
}
|
|
}
|
|
json.put("properties", nodePropertiesShortQNames);
|
|
}
|
|
else
|
|
{
|
|
json.put("properties", nodeProperties);
|
|
}
|
|
|
|
// add aspects as an array
|
|
Set<QName> nodeAspects = this.nodeService.getAspects(this.nodeRef);
|
|
if (useShortQNames)
|
|
{
|
|
Set<String> nodeAspectsShortQNames
|
|
= new LinkedHashSet<String>(nodeAspects.size());
|
|
for (QName nextLongQName : nodeAspects)
|
|
{
|
|
String nextShortQName = getShortQName(nextLongQName);
|
|
nodeAspectsShortQNames.add(nextShortQName);
|
|
}
|
|
json.put("aspects", nodeAspectsShortQNames);
|
|
}
|
|
else
|
|
{
|
|
json.put("aspects", nodeAspects);
|
|
}
|
|
}
|
|
catch (JSONException error)
|
|
{
|
|
error.printStackTrace();
|
|
}
|
|
|
|
jsonStr = json.toString();
|
|
}
|
|
}
|
|
|
|
return jsonStr;
|
|
}
|
|
|
|
/**
|
|
* Returns the JSON representation of this node. Long-form QNames are used in the
|
|
* result.
|
|
*
|
|
* @return The JSON representation of this node
|
|
*/
|
|
public String toJSON()
|
|
{
|
|
return this.toJSON(false);
|
|
}
|
|
|
|
/**
|
|
* Given a long-form QName, this method uses the namespace service to create a
|
|
* short-form QName string.
|
|
*
|
|
* @param longQName
|
|
* @return the short form of the QName string, e.g. "cm:content"
|
|
*/
|
|
protected String getShortQName(QName longQName)
|
|
{
|
|
return longQName.toPrefixString(services.getNamespaceService());
|
|
}
|
|
|
|
/**
|
|
* Helper to create a QName from either a fully qualified or short-name QName string
|
|
*
|
|
* @param s Fully qualified or short-name QName string
|
|
*
|
|
* @return QName
|
|
*/
|
|
protected QName createQName(String s)
|
|
{
|
|
QName qname;
|
|
if (s.indexOf(NAMESPACE_BEGIN) != -1)
|
|
{
|
|
qname = QName.createQName(s);
|
|
}
|
|
else
|
|
{
|
|
qname = QName.createQName(s, this.services.getNamespaceService());
|
|
}
|
|
return qname;
|
|
}
|
|
|
|
/**
|
|
* Reset the Node cached state
|
|
*/
|
|
public void reset()
|
|
{
|
|
this.name = null;
|
|
this.type = null;
|
|
this.properties = null;
|
|
this.aspects = null;
|
|
this.targetAssocs = null;
|
|
this.sourceAssocs = null;
|
|
this.childAssocs = null;
|
|
this.children = null;
|
|
this.hasChildren = null;
|
|
this.parentAssocs = null;
|
|
this.displayPath = null;
|
|
this.isDocument = null;
|
|
this.isContainer = null;
|
|
this.parent = null;
|
|
this.primaryParentAssoc = null;
|
|
this.activeWorkflows = null;
|
|
this.siteName = null;
|
|
this.siteNameResolved = false;
|
|
}
|
|
|
|
/**
|
|
* @return helper object to perform cross repository copy of JavaScript Node objects
|
|
*/
|
|
protected CrossRepositoryCopy getCrossRepositoryCopyHelper()
|
|
{
|
|
return (CrossRepositoryCopy)this.services.getService(
|
|
QName.createQName("", CrossRepositoryCopy.BEAN_NAME));
|
|
}
|
|
|
|
/**
|
|
* Return a list or a single Node from executing an xpath against the parent Node.
|
|
*
|
|
* @param xpath XPath to execute
|
|
* @param firstOnly True to return the first result only
|
|
*
|
|
* @return Object[] can be empty but never null
|
|
*/
|
|
private Object[] getChildrenByXPath(String xpath, QueryParameterDefinition[] params, boolean firstOnly)
|
|
{
|
|
Object[] result = null;
|
|
|
|
if (xpath.length() != 0)
|
|
{
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
logger.debug("Executing xpath: " + xpath);
|
|
if (params != null)
|
|
{
|
|
for (int i=0; i<params.length; i++)
|
|
{
|
|
logger.debug(" [" + params[i].getQName() + "]=" + params[i].getDefault());
|
|
}
|
|
}
|
|
}
|
|
|
|
List<NodeRef> nodes = this.services.getSearchService().selectNodes(this.nodeRef, xpath, params,
|
|
this.services.getNamespaceService(), false);
|
|
|
|
// see if we only want the first result
|
|
if (firstOnly == true)
|
|
{
|
|
if (nodes.size() != 0)
|
|
{
|
|
result = new Object[1];
|
|
result[0] = newInstance(nodes.get(0), this.services, this.scope);
|
|
}
|
|
}
|
|
// or all the results
|
|
else
|
|
{
|
|
result = new Object[nodes.size()];
|
|
for (int i = 0; i < nodes.size(); i++)
|
|
{
|
|
NodeRef ref = nodes.get(i);
|
|
result[i] = newInstance(ref, this.services, this.scope);
|
|
}
|
|
}
|
|
}
|
|
|
|
return result != null ? result : new Object[0];
|
|
}
|
|
|
|
/**
|
|
* Helper to return true if the supplied property value is a ScriptContentData object
|
|
*
|
|
* @param o Object to test
|
|
*
|
|
* @return true if instanceof ScriptContentData, false otherwise
|
|
*/
|
|
public boolean isScriptContent(Object o)
|
|
{
|
|
return (o instanceof ScriptContentData);
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------------------
|
|
// Value Conversion
|
|
|
|
/**
|
|
* Gets the node value converter
|
|
*
|
|
* @return the node value converter
|
|
*/
|
|
protected NodeValueConverter getValueConverter()
|
|
{
|
|
if (converter == null)
|
|
{
|
|
converter = createValueConverter();
|
|
}
|
|
return converter;
|
|
}
|
|
|
|
/**
|
|
* Constructs the node value converter
|
|
*
|
|
* @return the node value converter
|
|
*/
|
|
protected NodeValueConverter createValueConverter()
|
|
{
|
|
return new NodeValueConverter();
|
|
}
|
|
|
|
// Set support
|
|
|
|
/**
|
|
* Value converter with knowledge of Node specific value types
|
|
*/
|
|
public class NodeValueConverter extends ValueConverter
|
|
{
|
|
/**
|
|
* Convert an object from any repository serialized value to a valid script object. This includes converting
|
|
* Collection multi-value properties into JavaScript Array objects.
|
|
*
|
|
* @param qname QName of the property value for conversion
|
|
* @param value Property value
|
|
*
|
|
* @return Value safe for scripting usage
|
|
*/
|
|
public Serializable convertValueForScript(QName qname, Serializable value)
|
|
{
|
|
return convertValueForScript(services, scope, qname, value);
|
|
}
|
|
|
|
/*
|
|
* (non-Javadoc)
|
|
*
|
|
* @see org.alfresco.repo.jscript.ValueConverter#convertValueForScript(org.alfresco.service.ServiceRegistry,
|
|
* org.mozilla.javascript.Scriptable, org.alfresco.service.namespace.QName, java.io.Serializable)
|
|
*/
|
|
@Override
|
|
public Serializable convertValueForScript(ServiceRegistry services, Scriptable scope, QName qname,
|
|
Serializable value)
|
|
{
|
|
if (value instanceof ContentData)
|
|
{
|
|
// ContentData object properties are converted to ScriptContentData objects
|
|
// so the content and other properties of those objects can be accessed
|
|
value = new ScriptContentData((ContentData) value, qname);
|
|
}
|
|
else
|
|
{
|
|
value = super.convertValueForScript(services, scope, qname, value);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
/*
|
|
* (non-Javadoc)
|
|
*
|
|
* @see org.alfresco.repo.jscript.ValueConverter#convertValueForRepo(java.io.Serializable)
|
|
*/
|
|
@Override
|
|
public Serializable convertValueForRepo(Serializable value)
|
|
{
|
|
if (value instanceof ScriptContentData)
|
|
{
|
|
// convert back to ContentData
|
|
value = ((ScriptContentData) value).contentData;
|
|
}
|
|
else
|
|
{
|
|
value = super.convertValueForRepo(value);
|
|
}
|
|
return value;
|
|
}
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------------------
|
|
// Inner Classes
|
|
|
|
|
|
/**
|
|
* Inner class wrapping and providing access to a ContentData property
|
|
*/
|
|
public class ScriptContentData implements Content, Serializable
|
|
{
|
|
private static final long serialVersionUID = -7819328543933312278L;
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @param contentData The ContentData object this object wraps
|
|
* @param property The property the ContentData is attached too
|
|
*/
|
|
public ScriptContentData(ContentData contentData, QName property)
|
|
{
|
|
this.contentData = contentData;
|
|
this.property = property;
|
|
}
|
|
|
|
/* (non-Javadoc)
|
|
* @see org.alfresco.repo.jscript.ScriptNode.ScriptContent#getContent()
|
|
*/
|
|
public String getContent()
|
|
{
|
|
ContentService contentService = services.getContentService();
|
|
ContentReader reader = contentService.getReader(nodeRef, property);
|
|
return (reader != null && reader.exists()) ? reader.getContentString() : "";
|
|
}
|
|
|
|
/*
|
|
* (non-Javadoc)
|
|
* @see org.springframework.extensions.surf.util.Content#getInputStream()
|
|
*/
|
|
public InputStream getInputStream()
|
|
{
|
|
ContentService contentService = services.getContentService();
|
|
ContentReader reader = contentService.getReader(nodeRef, property);
|
|
return (reader != null && reader.exists()) ? reader.getContentInputStream() : null;
|
|
}
|
|
|
|
/*
|
|
* (non-Javadoc)
|
|
* @see org.springframework.extensions.surf.util.Content#getReader()
|
|
*/
|
|
public Reader getReader()
|
|
{
|
|
ContentService contentService = services.getContentService();
|
|
ContentReader reader = contentService.getReader(nodeRef, property);
|
|
|
|
if (reader != null && reader.exists())
|
|
{
|
|
try
|
|
{
|
|
return (contentData.getEncoding() == null) ? new InputStreamReader(reader.getContentInputStream()) : new InputStreamReader(reader.getContentInputStream(), contentData.getEncoding());
|
|
}
|
|
catch (IOException e)
|
|
{
|
|
// NOTE: fall-through
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Set the content stream
|
|
*
|
|
* @param content Content string to set
|
|
*/
|
|
public void setContent(String content)
|
|
{
|
|
ContentService contentService = services.getContentService();
|
|
ContentWriter writer = contentService.getWriter(nodeRef, this.property, true);
|
|
writer.setMimetype(getMimetype()); // use existing mimetype value
|
|
writer.putContent(content);
|
|
|
|
// update cached variables after putContent()
|
|
this.contentData = (ContentData) services.getNodeService().getProperty(nodeRef, this.property);
|
|
}
|
|
|
|
/**
|
|
* Set the content stream from another content object.
|
|
*
|
|
* @param content ScriptContent to set
|
|
*/
|
|
public void write(Content content)
|
|
{
|
|
ContentService contentService = services.getContentService();
|
|
ContentWriter writer = contentService.getWriter(nodeRef, this.property, true);
|
|
writer.setMimetype(content.getMimetype());
|
|
writer.setEncoding(content.getEncoding());
|
|
writer.putContent(content.getInputStream());
|
|
|
|
// update cached variables after putContent()
|
|
this.contentData = (ContentData) services.getNodeService().getProperty(nodeRef, this.property);
|
|
}
|
|
|
|
/**
|
|
* Set the content stream from another content object.
|
|
*
|
|
* @param content ScriptContent to set
|
|
* @param applyMimetype If true, apply the mimetype from the Content object, else leave the original mimetype
|
|
* @param guessEncoding If true, guess the encoding from the underlying input stream, else use encoding set in
|
|
* the Content object as supplied.
|
|
*/
|
|
public void write(Content content, boolean applyMimetype, boolean guessEncoding)
|
|
{
|
|
ContentService contentService = services.getContentService();
|
|
ContentWriter writer = contentService.getWriter(nodeRef, this.property, true);
|
|
InputStream is = null;
|
|
if (applyMimetype)
|
|
{
|
|
writer.setMimetype(content.getMimetype());
|
|
}
|
|
if (guessEncoding)
|
|
{
|
|
is = new BufferedInputStream(content.getInputStream());
|
|
is.mark(1024);
|
|
writer.setEncoding(guessEncoding(is, false));
|
|
try
|
|
{
|
|
is.reset();
|
|
}
|
|
catch (IOException e)
|
|
{
|
|
}
|
|
}
|
|
else
|
|
{
|
|
writer.setEncoding(content.getEncoding());
|
|
is = content.getInputStream();
|
|
}
|
|
writer.putContent(is);
|
|
|
|
// update cached variables after putContent()
|
|
this.contentData = (ContentData) services.getNodeService().getProperty(nodeRef, this.property);
|
|
}
|
|
|
|
/**
|
|
* Set the content stream from another input stream.
|
|
*
|
|
* @param inputStream
|
|
*/
|
|
public void write(InputStream inputStream)
|
|
{
|
|
ContentService contentService = services.getContentService();
|
|
ContentWriter writer = contentService.getWriter(nodeRef, this.property, true);
|
|
writer.putContent(inputStream);
|
|
|
|
// update cached variables after putContent()
|
|
this.contentData = (ContentData) services.getNodeService().getProperty(nodeRef, this.property);
|
|
}
|
|
|
|
/**
|
|
* Delete the content stream
|
|
*/
|
|
public void delete()
|
|
{
|
|
ContentService contentService = services.getContentService();
|
|
ContentWriter writer = contentService.getWriter(nodeRef, this.property, true);
|
|
OutputStream output = writer.getContentOutputStream();
|
|
try
|
|
{
|
|
output.close();
|
|
}
|
|
catch (IOException e)
|
|
{
|
|
// NOTE: fall-through
|
|
}
|
|
writer.setMimetype(null);
|
|
writer.setEncoding(null);
|
|
|
|
// update cached variables after putContent()
|
|
this.contentData = (ContentData) services.getNodeService().getProperty(nodeRef, this.property);
|
|
}
|
|
|
|
/**
|
|
* @return download URL to the content
|
|
*/
|
|
public String getUrl()
|
|
{
|
|
return MessageFormat.format(CONTENT_PROP_URL, new Object[] { nodeRef.getStoreRef().getProtocol(),
|
|
nodeRef.getStoreRef().getIdentifier(), nodeRef.getId(),
|
|
URLEncoder.encode(getName()),
|
|
URLEncoder.encode(property.toString()) });
|
|
}
|
|
|
|
/**
|
|
* @return download URL to the content for a document item only
|
|
*/
|
|
public String getDownloadUrl()
|
|
{
|
|
if (getIsDocument() == true)
|
|
{
|
|
return MessageFormat.format(CONTENT_DOWNLOAD_PROP_URL, new Object[] {
|
|
nodeRef.getStoreRef().getProtocol(),
|
|
nodeRef.getStoreRef().getIdentifier(),
|
|
nodeRef.getId(),
|
|
URLEncoder.encode(getName()),
|
|
URLEncoder.encode(property.toString()) });
|
|
}
|
|
else
|
|
{
|
|
return "";
|
|
}
|
|
}
|
|
|
|
public long getSize()
|
|
{
|
|
return contentData.getSize();
|
|
}
|
|
|
|
public String getMimetype()
|
|
{
|
|
return contentData.getMimetype();
|
|
}
|
|
|
|
public String getEncoding()
|
|
{
|
|
return contentData.getEncoding();
|
|
}
|
|
|
|
public void setEncoding(String encoding)
|
|
{
|
|
this.contentData = ContentData.setEncoding(this.contentData, encoding);
|
|
services.getNodeService().setProperty(nodeRef, this.property, this.contentData);
|
|
|
|
// update cached variables after putContent()
|
|
this.contentData = (ContentData) services.getNodeService().getProperty(nodeRef, this.property);
|
|
}
|
|
|
|
public void setMimetype(String mimetype)
|
|
{
|
|
this.contentData = ContentData.setMimetype(this.contentData, mimetype);
|
|
services.getNodeService().setProperty(nodeRef, this.property, this.contentData);
|
|
|
|
// update cached variables after putContent()
|
|
this.contentData = (ContentData) services.getNodeService().getProperty(nodeRef, this.property);
|
|
}
|
|
|
|
/**
|
|
* Guess the mimetype for the given filename - uses the extension to match on system mimetype map
|
|
*/
|
|
public void guessMimetype(String filename)
|
|
{
|
|
ContentService contentService = services.getContentService();
|
|
ContentReader reader = contentService.getReader(nodeRef, property);
|
|
setMimetype(services.getMimetypeService().guessMimetype(filename, reader));
|
|
}
|
|
|
|
/**
|
|
* Guess the character encoding of a file. For non-text files UTF-8 default is applied, otherwise
|
|
* the appropriate encoding (such as UTF-16 or similar) will be appiled if detected.
|
|
*/
|
|
public void guessEncoding()
|
|
{
|
|
setEncoding(guessEncoding(getInputStream(), true));
|
|
}
|
|
|
|
private String guessEncoding(InputStream in, boolean close)
|
|
{
|
|
String encoding = "UTF-8";
|
|
try
|
|
{
|
|
if (in != null)
|
|
{
|
|
Charset charset = services.getMimetypeService().getContentCharsetFinder().getCharset(in, getMimetype());
|
|
encoding = charset.name();
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
try
|
|
{
|
|
if (close && in != null)
|
|
{
|
|
in.close();
|
|
}
|
|
}
|
|
catch (IOException ioErr)
|
|
{
|
|
}
|
|
}
|
|
return encoding;
|
|
}
|
|
|
|
private ContentData contentData;
|
|
private QName property;
|
|
}
|
|
|
|
/**
|
|
* Interface contract for simple anonymous classes that implement document transformations
|
|
*/
|
|
private interface Transformer
|
|
{
|
|
/**
|
|
* Transform the reader to the specified writer
|
|
*
|
|
* @param contentService ContentService
|
|
* @param noderef NodeRef of the destination for the transform
|
|
* @param reader Source reader
|
|
* @param writer Destination writer
|
|
*
|
|
* @return Node representing the transformed entity
|
|
*/
|
|
ScriptNode transform(ContentService contentService, NodeRef noderef, ContentReader reader, ContentWriter writer);
|
|
}
|
|
|
|
|
|
/**
|
|
* NamespacePrefixResolverProvider getter implementation
|
|
*/
|
|
public NamespacePrefixResolver getNamespacePrefixResolver()
|
|
{
|
|
return this.services.getNamespaceService();
|
|
}
|
|
} |