mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-06-30 18:15:39 +00:00
43628: Fix for ALF-16299 - On full user profile, in the 'Sites' tab, the site descriptions are not aligned when a long site description is provided. 43639: ALF-16701: use newly released Spring Surf 1.2.0-M1 rather than a SNAPSHOT 43644: ALF-16527: webscript person.lib.ftl does not include all attributes for users out of the box. - added missing organizationId attribute. 43649: ALF-16756: WebDAV: An error occurs on drag&drop content from local machine to alfresco when inbound move rule configured. 43651: ALF-16756: Fixed typos - I took this code in good faith! 43659: ALF-16006: MT: Document Library is absent after upgrade from 3.4.x to 4.1.x (eg. 3.4.10 -> 4.1.1) - More patch dependencies required 43666: ALF-16833 / MNT-187: Fixed regression in inbound rule firing caused by the fix to ALF-14744 - Inbound rules were not firing for newly created content nodes that had null content properties (e.g. dataListItems) - Now the ALF-14744 fix has been revised so that inbound rules are only not fired for new nodes with zero-length content, as possibly created by the OSX / Windows 7 WebDAV clients during an upload, and only if policy.content.update.ignoreEmpty=true (the default) - OnContentUpdateRuleTrigger removed. Now all content property updates are handled by OnPropertyUpdateRuleTrigger so that we can have this subtle treatment of 'empty' content properties. - Reverted ALF-14744 changes to RuleServiceCoverageTest and added new tests for 'empty' content and ASPECT_NO_CONTENT - Updated RuleTriggerTest in line with changes 43675: Merged V4.1 to V4.1-BUG-FIX 43674: Merged PATCHES/V4.1.1 to V4.1 43673: Merged V3.4-BUG-FIX to PATCHES/V4.1.1 43672: ALF-16834, ALF-16833, MNT-187: Fix unit test failures and 'fine tune' logic to handle multiple updates within a single transaction creating a node 43676: ALF-12324: Can't delete site with WQS content - Don't try to add nodes to the publish queue on site deletion 43689: Fix for ALF-14207 - cm:link not correctly handled in Share's doclist when users don't have permission 43690: Fix for ALF-16538 - Wrong label on change group permission message 43696: ALF-16348 (Inconsistent error message when latest manager leaves a site) 43719: ALF-16031: "CMISChangeLog does not log Created events" 43720: ALF-14137: "When calling CMIS getAllVersions method using the OpenCMIS Browser the PreparedStatement is executed multiple times" 43722: ALF-16352: Re-instated manage permissions action to document list view 43745: Fix for ALF-15351. ContentModelFormProcessor had a TODO to handle associations defined on aspects other than those aspects already on the node for which a form is being submitted. I've added code that, when an association is added to a node, will check if the association is defined on any aspect in the system and if it is, it will let the association be created. 43757: ALF-16171: If a password for admin user is specified with a space in the end during the installation Alfresco startup fails - Added validation that will not allow the specified password to contain leading to trailing white space 43760: Fixed ALF-16317 "Labels not displaying full text when creating a rule on a folder with IE8" part 2 43769: Fix for ALF-13461. Merged PATCHES/V3.4.8 to BRANCHES/DEV/V4.1-BUG-FIX: 43344: MNT-162: Merged DEV to PATCHES/V3.4.8 43254: MNT-162: CLONE - Hidden folder "Comments" is available to searching - Removed last '/' from COMMENT_QNAMEPATH constant. 43770: ALF-15616: Merged V3.4-BUG-FIX (3.4.12) to V4.1-BUG-FIX (4.1.3) 43768: Merged Dev to V3.4-BUG-FIX (3.4.12) 43743: ALF-11956: WCM accessibility The problem with absence of initial focus on the first Web form element has been resolved in 'alfresco.xforms.XForm._loadHandler()'. This method creates all controls, defined in the form. 'alfresco.xforms.FocusResolver._findControl()' has been renamed to a public-like method ('alfresco.xforms.FocusResolver.findControl()'). Also it has been modified to introduce a possibility of searching elements, using root XForms widgets container. Some other minor fixes. 43772: Fixed ALF-16497 "Selected Items drop down box is expanded to the right in IE9" 43787: Merged in Philippe's fix for ALF-16313:unmakeTranslation() called on pivot language gives exception 43794: ALF-16155: "Notification digest accumulates and is repeatedly sent if 1 or more notification email fails" 43805: ALF-16212: Fixes double encoding issue. 43835: Fixed ALF-13595: Patches third party library date.js to prevent infinite loop. 43836: ALF-16480: Merged PATCHES/V4.1.1 to V4.1-BUG-FIX 43252: MNT-166: Document lock not removed after the lock expiry date has been reached - Changed evaluator "evaluator.doclib.metadata.isLocked" to use "evaluator.doclib.indicator.nodeLocked" and "evaluator.doclib.indicator.lockOwner" evaluator. 43253: MNT-165: "Cancel Editing" does not completely remove lock from document - Added documentLibrary cancel editing action for locked documents. 43300: MNT-171: Merged V4.1-BUG-FIX to PATCHES/V4.1.1 (modified) 43096: Fix for ALF-16283 - When document is checked out, 'Edit Online' and 'Upload New Version' options should not be visible on the original document. 43311: MNT-165: "Cancel Editing" does not completely remove lock from document - Fix for page refresh problem when cancel editing on details page 43421: MNT-186: 4.1.1.7 HF: Webscipt error on doclib page, containing locked by other users files - Change evaluator.doclib.metadata.isLocked to break circular dependency 43755: MNT-202: Upload New Version not available for a document that has been edited offline - Upload New Version is now available if editable by user (respecting locks, type of checkout, etc). 43844: ALF-16696: Merged DEV to V4.1-BUG-FIX 43734: Share nodebrowser is unable to access node of a document with MS residual properties - Use localname if no prefix is registered for a namespace uri 43864: Fixed ALF-16320 "Properties side panel collapses after editing properties in document preview window with IE8" 43866: Fixed ALF-16320 "Properties side panel collapses after editing properties in document preview window with IE8" part 2 - Checking using YAHOO.util.Event.getTarget instead 43867: Fixed ALF-16276 "'"Web View' dahslet displays 2 scrollbars in IE8." 43872: Merged V4.1 to V4.1-BUG-FIX 43622: ALF-16757: Sharepoint doesn't work correct with SSO - Fix by Pavel 43633: Latest translations from Gloria (r43623) 43636: Merged PATCHES/V4.1.1 to V4.1 43301: ALF-16811 / MNT-173: SOLR tracking spending too long evaluating paths - Too much time was being spent fetching the individual nodes in each path and there could be potentially thousands - Now we traverse all the ancestor parent associations in the cache before switching shared cache reads off, then bulk load them in one shot! 43303: ALF-16812 / MNT-174: "dictionaryDAO.init() called, yet no namespace registry for domain" after node rejoins cluster - namespaceRegistryCache is secondary to the clustered dictionaryRegistryCache, so can be a non-clustered cache. Synchronization and thread locals already in use so still thread safe. - dictionaryDAO.init() now has sole responsibility of establishing the NamespaceRegistry threadlocal within its locks, so no more risk of cyclic dependencies, race conditions, partial initialization, or registries dropping out of the cache - To avoid being confused by the 'consistent read' behaviour of a transactional cache, DictionaryDAOImpl now reads / writes directly to a shared cache. Again locks and thread locals still used so still thread safe 43334: ALF-16812 / MNT-174: Fixed failing unit tests - Because DictionaryDAOImpl now reads directly from the shared cache we need to reset it on initialization to avoid problems in multi-context unit tests 43337: ALF-16811 / MNT-173: Fix test failure. Cope with IDs of deleted nodes in getCachedAncestors() 43356: ALF-16811 / MNT-173: If we are disabling shared cache reads for the transaction, convert all existing reads and updates to avoid 'consistent read' behaviour giving us a potentially out of date node already accessed - Existing read buckets are simply thrown away - Updates are converted to removes to avoid any assumptions about existing shared cache content - New entries are left alone as they haven't come from the shared cache anyway 43410: ALF-16813 / MNT-185: Web Scripts are being endlessly re-registering in clustered environment - Due to 'consistent read' behaviour of transactional cache - As accesses are regulated by RW locks we can read straight through to the shared cache instead 43565: ALF-16814 / MNT-190: Bmlab Solr Node 2 threw unhandled NullPointerException (and possibly made solr unresponsive) - Added missing return statement 43646: ALF-15755: Rationalization of WebDAVMethod.checkNode() 43681: Merged HEAD to V4.1 43656: Fix for ALF-16683 CMIS: cannot navigate to original document was created through CMIS with CHECKED OUT version state. 43698: Latest Russian translations from Gloria 43838: ALF-16875: Merged V4.1-BUG-FIX to V4.1 43836: ALF-16480: Merged PATCHES/V4.1.1 to V4.1-BUG-FIX 43252: MNT-166: Document lock not removed after the lock expiry date has been reached - Changed evaluator "evaluator.doclib.metadata.isLocked" to use "evaluator.doclib.indicator.nodeLocked" and "evaluator.doclib.indicator.lockOwner" evaluator. 43253: MNT-165: "Cancel Editing" does not completely remove lock from document - Added documentLibrary cancel editing action for locked documents. 43300: MNT-171: Merged V4.1-BUG-FIX to PATCHES/V4.1.1 (modified) 43096: Fix for ALF-16283 - When document is checked out, 'Edit Online' and 'Upload New Version' options should not be visible on the original document. 43311: MNT-165: "Cancel Editing" does not completely remove lock from document - Fix for page refresh problem when cancel editing on details page 43421: MNT-186: 4.1.1.7 HF: Webscipt error on doclib page, containing locked by other users files - Change evaluator.doclib.metadata.isLocked to break circular dependency 43755: ALF-16890 / MNT-202: Upload New Version not available for a document that has been edited offline - Upload New Version is now available if editable by user (respecting locks, type of checkout, etc). 43868: Reverse merging r43838 - Merge in wrong direction introducing duplicate fragment into share-documentlibrary-config.xml 43871: ALF-16890: Merged PATCHES/V4.1.1 to V4.1 43755: MNT-202: Upload New Version not available for a document that has been edited offline - Upload New Version is now available if editable by user (respecting locks, type of checkout, etc). 43873: Merged V4.1 to V4.1-BUG-FIX (RECORD ONLY) 43602: ALF-16254: Merged V4.1-BUG-FIX to V4.1 43598: Merged HEAD to BRANCHES/DEV/V4.1-BUG-FIX 41906: ALF-11378: REST API has been modified to return extra information about a user whether s/he belongs to a group or not. 43612: ALF-16598: Merged V4.1-BUG-FIX to V4.1 43252: MNT-166: Document lock not removed after the lock expiry date has been reached - Changed evaluator "evaluator.doclib.metadata.isLocked" to use "evaluator.doclib.indicator.nodeLocked" and "evaluator.doclib.indicator.lockOwner" evaluator. 43254: MNT-165: "Cancel Editing" does not completely remove lock from document - Added documentLibrary cancel editing action for locked documents. 43300: MNT-171: Merged V4.1-BUG-FIX to PATCHES/V4.1.1 (modified) 43096: Fix for ALF-16283 - When document is checked out, 'Edit Online' and 'Upload New Version' options should not be visible on the original document. 43311: MNT-165: "Cancel Editing" does not completely remove lock from document - Fix for page refresh problem when cancel editing on details page 43421: MNT-186: 4.1.1.7 HF: Webscipt error on doclib page, containing locked by other users files - Change evaluator.doclib.metadata.isLocked to break circular dependency 43615: ALF-16794: Merged V4.1-BUG-FIX to V4.1 43478: MNT-181: Now WebDAV will ALWAYS preserve the original metadata and versions of ANY node that is temporarily 'moved out' in ANY kind of 'shuffle' operation - To make the source node temporarily invisible to WebDAV the client specific HIDDEN aspect features are used - WebDAVHelper.isRenameShuffle() method introduced, to parallel ALF-3856 CIFS fix and using similar system.webdav.renameShufflePattern global property to detect the start of a shuffle - WebDAVHelper converted to use proper dependency injection - CopyMethod has become a simple subclass of MoveMethod as all the hidden aspect munging is done by it - DeleteMethod now preserves hidden nodes - PropFindMethod now ignores hidden nodes - Listing methods will hide hidden nodes from WebDAV 43483: MNT-181: Corrected typo 43523: MNT-181: Corrections - WebDAVLockService.unlock() made 'harmless' to call on already-unlocked nodes - Delete method hides rather than deletes versioned nodes and working copes in case it is called by OSX Finder during a 'replace' operation 43524: MNT-181: Correction - PutMethod now 'unhides' hidden nodes and behaves as though it created them 43570: MNT-181: More corrections researched by Valery - Don't treat all moves to temporary locations as copies - just those from non-temporary locations. Avoids initial upload leaving lots of hidden files around. - Only copy the content, not the whole node including aspects to avoid versioning temporary files! - Don't version on changes to sys:clientVisibilityMask - avoids 'double versioning' - Recognize Mac .TemporaryItems folder and ._ files as temporary 43586: MNT-181: Final correction researched by Valery - Corrected system.webdav.renameShufflePattern so that it matches .TemporaryItems folder and ._ files as a full match 43616: ALF-15755: Merged V4.1-BUG-FIX to V4.1 43591: ALF-16772: If the WebDAV path of a document exceeds 255 characters, documents opened in MSOffice cannot be saved back - Interpret null nodeLockToken as not locked. 43629: Merged V4.1-BUG-FIX to V4.1 (4.1.2) 43498: Fix for ALF-16648 - Alfresco Enterprise artifacts in artifacts.alfresco.com do not provide POM files / dependencies declarations: Merged HEAD to V4.1-BUG-FIX (4.1.2) 43380: -- added site content for alfresco-platform-distribution POM 43379: -- added site documentation for alfresco-platform-distribution POM 43378: -- added site documentation for alfresco-platform-distribution POM -- deployed site for 4.2.b Community at https://artifacts.alfresco.com/nexus/content/repositories/alfresco-docs/alfresco-platform-distribution/latest/index.html -- created repository for Enterprise docs and added url in the appropriate edition properties 43273: Use property to define POI version 42966: ALF-14353 - Added platform distribution POM to standard maven-deploy procedure 42965: ALF-14353 - added alfresco-platform-distribution to provide a Maven release descriptor (dependencyManagement) per each Community / Enterprise release -- moved maven-ant-tasks not to be in the runtime lib -- added platform distribution pom in the SDK folder -- updated maven.xml to deploy filter and deploy the appropriate platform-distribution POM per each releae -- in maven.xml moved configure-release and configure-snapshot goals to maven-env-prerequisites -- updated sdk readme to explain the presence of alfresco-platform-distribution POM 42912: -- updated README header on the POM specifying it's NOT usable to build Alfresco -- make a clear reference to the POMs that get deployed by pom-experimental.xml being usable for development 42842: ALF-14353: Fix artifactId alfresco-jlan -> alfresco-jlan-embed 41883: ALF-14353 - fixed multiple Maven build issues. Now mvn clean install -f pom-experimental.xml works fine. Also deployed Spring Surf 1.2.0-SNAPSHOT so proper Surf version is retrieved 41882: added pre-requisites to build POMs successfully with mvn clean install -f pom-experimental.xml 43634: Merged V4.1-BUG-FIX to V4.1 43386: ALF-13091: Prevent bean post processor propagation to child application contexts. Remove Jsr250BeanPostPorcessor from the CXF configuration, to prevent strange interaction with component scanning. 43641: Merged V4.1-BUG-FIX to V4.1 (4.1.2) 43639: ALF-16701: use newly released Spring Surf 1.2.0-M1 in POM files rather than a SNAPSHOT 43645: Merged V4.1-BUG-FIX to V4.1 43644: ALF-16527: webscript person.lib.ftl does not include all attributes for users out of the box. - added missing organizationId attribute. 43660: Merged V4.1-BUG-FIX to V4.1 43659: ALF-16006: MT: Document Library is absent after upgrade from 3.4.x to 4.1.x (eg. 3.4.10 -> 4.1.1) - More patch dependencies required 43669: Merged V4.1-BUG-FIX to V4.1 43666: ALF-16833 / MNT-187: Fixed regression in inbound rule firing caused by the fix to ALF-14744 - Inbound rules were not firing for newly created content nodes that had null content properties (e.g. dataListItems) - Now the ALF-14744 fix has been revised so that inbound rules are only not fired for new nodes with zero-length content, as possibly created by the OSX / Windows 7 WebDAV clients during an upload, and only if policy.content.update.ignoreEmpty=true (the default) - OnContentUpdateRuleTrigger removed. Now all content property updates are handled by OnPropertyUpdateRuleTrigger so that we can have this subtle treatment of 'empty' content properties. - Reverted ALF-14744 changes to RuleServiceCoverageTest and added new tests for 'empty' content and ASPECT_NO_CONTENT - Updated RuleTriggerTest in line with changes 43697: Merged V4.1-BUG-FIX to V4.1 43689: Fix for ALF-14207 - cm:link not correctly handled in Share's doclist when users don't have permission 43761: Merged V4.1-BUG-FIX to V4.1 43760: Fixed ALF-16317 "Labels not displaying full text when creating a rule on a folder with IE8" part 2 43796: Merged V4.1-BUG-FIX to V4.1 43795: Fix for ALF-16254 - "Leave Site" behaviour for group based site membership: Blah, Blah, Blah! 43883: Fix for ALF-12711. Separated preparation of email message from sending of email message as described in analysis in JIRA. Did not merge the fix provided as MailActionExecuter.java.diff but reimplemented a fix based on that. 43888: ALF-16781: Merged V3.4-BUG-FIX (3.4.12) to V4.1-BUG-FIX (4.1.3) 43887: ALF-16898 CLONE - Transformation Server history shows incorrect transformation "To" type for images - ImageTransformActionExecuter ("Transform and Copy Image") re-factored to use ContentService rather than hard coded to ImageMagick 43900: Merged V4.1 to V4.1-BUG-FIX 43898: Merged PATCHES/V4.1.1 to V4.1 43708: ALF-16903 / MNT-203: ACL changes not propagated between two non clustered Alfresco instances hitting same Solr and Database - Although SOLRTrackingComponentImpl was not trusting the cache when tracking node transactions, it was still trusting the cache for ACL change sets - This mean that when the SOLR node is using an Alfresco out of the cluster (which we are supposed to support) it could see stale ACLs - Fixed this by using the same strategy we use in AbstractNodeDAO - Now the main acl CRUD cache is set to ignore the shared cache during SOLR tracking - All secondary ACL caches are keyed by ACL ID AND version, so it prevents the possibility of retrieving a stale cached ACL for an old version 43713: ALF-16903 / MNT-203: ACL changes not propagated between two non clustered Alfresco instances hitting same Solr and Database - Fix test failures - deleteAccessControlEntries must 'touch' all the ACLs it affects to keep caches valid - createAccessControlList should return the newly-versioned ACL 43736: ALF-16904 / MNT-204: Index showing no progress on Solr server - SAP's tracking thread got stuck indefinitely waiting on a content response after sending a request, probably due to some misbehaving proxy or balancer - Now we make it possible to recover from this - A new alfresco.socketTimeout parameter is now supported in solrcore.properties - It specifies the number of milliseconds SOLR will wait before giving up waiting for data on an HTTP connection - The default is still zero which means wait indefinitely 43759: ALF-16904 / MNT-204: Fixed compilation error 43899: Merged PATCHES/V4.1.1 to V4.1 (RECORD ONLY) 43667: Merged V4.1-BUG-FIX to PATCHES/V4.1.1 43666: ALF-16833 / MNT-187: Fixed regression in inbound rule firing caused by the fix to ALF-14744 - Inbound rules were not firing for newly created content nodes that had null content properties (e.g. dataListItems) - Now the ALF-14744 fix has been revised so that inbound rules are only not fired for new nodes with zero-length content, as possibly created by the OSX / Windows 7 WebDAV clients during an upload, and only if policy.content.update.ignoreEmpty=true (the default) - OnContentUpdateRuleTrigger removed. Now all content property updates are handled by OnPropertyUpdateRuleTrigger so that we can have this subtle treatment of 'empty' content properties. - Reverted ALF-14744 changes to RuleServiceCoverageTest and added new tests for 'empty' content and ASPECT_NO_CONTENT - Updated RuleTriggerTest in line with changes 43901: Merged V3.4-BUG-FIX to V4.1-BUG-FIX 43571: Merged DEV to V3.4-BUG-FIX 43569: ALF-16222: It's impossible to delete a file/message via IMAP using Microsoft Entourage 2008 in MacOSX 10.8 fixed: UID failed.Existing file or folder error on attempt to delete file if deleted items already contains file with the same name. modified: AttachmentExtractor modified to avoid code dublication. 43599: Fix for ALF-16505 - Discussion topics are sorted in ascending order (oldest first and newest last) when selecting 'All topics' 43610: Merged Dev to V3.4-BUGFIX (3.4.12) 43416: ALF-16470: SPP:Meeting recurent events are displayed incorrect in share calendar in all tabs (Day, Week, Month) Incorrect lucene query to search for events (it was search for events ONLY after fromDate, that gets from browser query. And recursive events, that have startDate before browser's query date, wasn't included into summary query for search for events. Start date is incorrect and is sets two times. Interval for month of end date is to small. Recursive event, that was started in previous month, and ends in current month, isn't included into result of search. For events, that ends on next date after start date, displays (view by month) only start date. 43625: ALF-11817: Cope with incomplete lock token headers from Microsoft-WebDAV-MiniRedir without the enclosing <> by just consuming the whole string 43670: ALF-11817: Prevent auto-hidden dot underscore files from reappearing on a put by checking for a shuffle path before 'unhiding' 43746: Merged DEV to V3.4-BUG-FIX (with corrections) 43692: ALF-16808 Webdav: Two versions of document have been added after the document has been rewritten once more via drag and drop action. 1. Checked whether the current content property is empty. 2. Disabled the versionable aspect. 3. Added the new content to the node. 43763: Fix for ALF-14828 - Incorrect behavior on delete action (WCMQS site) 43771: Fix for ALF-12752 - Custom form appearance parameters not accounted for causing ovverlapped textareas xforms changing their height 43773: Merged DEV to V3.4-BUG-FIX (3.4.12) 42010: ALF-14040: Event start/end time displays incorrect on MS Outlook Calendar and Calendar of created Meeting workspace 1) Send a date for "Site Calendar" dashlet in ISO-8601 format (like in v4.1.1), then after transformation to client's time zone it is displayed correct 2) For "My Calendar" dashlet we need to take into account that if the event is "all day event", then date should be used without time zone transformation 43804: ALF-12326 HomeFolderProviderSynchronizer fails to move any user space which has a rule configured on it 43837: Fixes ALF-12145: Date.js patches merged to 3.4 Merged BRANCHES/DEV/V4.0-BUG-FIX to BRANCHES/DEV/V3.4-BUG-FIX: 36202: ALF-13483: Japanese: Incorrect date handle in a date Input filed Merged BRANCHES/DEV/V4.1-BUG-FIX to BRANCHES/DEV/V3.4-BUG-FIX: 43835: Fixed ALF-13595: Patches third party library date.js to prevent infinite loop. 43839: ALF-16869: Merged PATCHES/V3.4.8 to V3.4-BUG-FIX 43344: MNT-162: Merged DEV to PATCHES/V3.4.8 43254: MNT-162: CLONE - Hidden folder "Comments" is available to searching - Removed last '/' from COMMENT_QNAMEPATH constant. 43902: Merged V3.4-BUG-FIX to V4.1-BUG-FIX (RECORD ONLY) 43177: Merged DEV to V3.4-BUG-FIX 43087: ALF-16474: Records Management groups were not deleted after removing RM site - Delete Records Management groups on ASPECT_RECORDS_MANAGEMENT_ROOT delete - Backport of RM-190 from RM 2.0 43228: ALF-16266: Merged HEAD to V3.4-BUG-FIX 32846: Fixes: ALF-10519: Issues with translation of roles. 31413: Fixes: ALF-10519 - Internationalises the role names for the repo browser's manage permissions page & makes these available to other pages through common.properties 43229: ALF-16266: Fix conflict data left in the properties file. 43624: ALF-11817: Merged PATCHES/V4.0.2 to V3.4-BUG-FIX 43587: Merged BRANCHES/V4.1-BUG-FIX to PATCHES/V4.0.2 42363: ALF-16213: renaming versioned file results in file being deleted. 43478: MNT-181: Now WebDAV will ALWAYS preserve the original metadata and versions of ANY node that is temporarily 'moved out' in ANY kind of 'shuffle' operation - To make the source node temporarily invisible to WebDAV the client specific HIDDEN aspect features are used - WebDAVHelper.isRenameShuffle() method introduced, to parallel ALF-3856 CIFS fix and using similar system.webdav.renameShufflePattern global property to detect the start of a shuffle - WebDAVHelper converted to use proper dependency injection - CopyMethod has become a simple subclass of MoveMethod as all the hidden aspect munging is done by it - DeleteMethod now preserves hidden nodes - PropFindMethod now ignores hidden nodes - Listing methods will hide hidden nodes from WebDAV 43483: MNT-181: Corrected typo 43523: MNT-181: Corrections - WebDAVLockService.unlock() made 'harmless' to call on already-unlocked nodes - Delete method hides rather than deletes versioned nodes and working copies in case it is called by OSX Finder during a 'replace' operation 43524: MNT-181: Correction - PutMethod now 'unhides' hidden nodes and behaves as though it created them 43570: MNT-181: More corrections researched by Valery - Don't treat all moves to temporary locations as copies - just those from non-temporary locations. Avoids initial upload leaving lots of hidden files around. - Only copy the content, not the whole node including aspects to avoid versioning temporary files! - Don't version on changes to sys:clientVisibilityMask - avoids 'double versioning' - Recognize Mac .TemporaryItems folder and ._ files as temporary 43586: MNT-181: Final correction researched by Valery - Corrected system.webdav.renameShufflePattern so that it matches .TemporaryItems folder and ._ files as a full match 43671: ALF-16834: Merged V4.1-BUG-FIX to V3.4-BUG-FIX 43666: ALF-16833 / MNT-187: Fixed regression in inbound rule firing caused by the fix to ALF-14744 - Inbound rules were not firing for newly created content nodes that had null content properties (e.g. dataListItems) - Now the ALF-14744 fix has been revised so that inbound rules are only not fired for new nodes with zero-length content, as possibly created by the OSX / Windows 7 WebDAV clients during an upload, and only if policy.content.update.ignoreEmpty=true (the default) - OnContentUpdateRuleTrigger removed. Now all content property updates are handled by OnPropertyUpdateRuleTrigger so that we can have this subtle treatment of 'empty' content properties. - Reverted ALF-14744 changes to RuleServiceCoverageTest and added new tests for 'empty' content and ASPECT_NO_CONTENT - Updated RuleTriggerTest in line with changes 43842: Merged V4.0-BUG-FIX to V3.4-BUG-FIX 33387: ALF-12492 - Email with empty subject sent to Alfresco by SMTP cause Null pointer Exception 43843: ALF-16717: Merged V4.1-BUG-FIX to V3.4-BUG-FIX 43314: ALF-16575 - Email server does not accept email where Subject ends with a period git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@43914 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
3006 lines
104 KiB
Java
3006 lines
104 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.opencmis;
|
|
|
|
import java.io.Serializable;
|
|
import java.math.BigDecimal;
|
|
import java.math.BigInteger;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Collections;
|
|
import java.util.Comparator;
|
|
import java.util.Date;
|
|
import java.util.EnumSet;
|
|
import java.util.GregorianCalendar;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Map.Entry;
|
|
import java.util.Set;
|
|
import java.util.TimeZone;
|
|
import java.util.TreeSet;
|
|
|
|
import javax.xml.datatype.DatatypeConfigurationException;
|
|
import javax.xml.datatype.DatatypeFactory;
|
|
|
|
import org.alfresco.model.ContentModel;
|
|
import org.alfresco.opencmis.ActivityPosterImpl.ActivityInfo;
|
|
import org.alfresco.opencmis.dictionary.CMISActionEvaluator;
|
|
import org.alfresco.opencmis.dictionary.CMISAllowedActionEnum;
|
|
import org.alfresco.opencmis.dictionary.CMISDictionaryService;
|
|
import org.alfresco.opencmis.dictionary.CMISNodeInfo;
|
|
import org.alfresco.opencmis.dictionary.CMISObjectVariant;
|
|
import org.alfresco.opencmis.dictionary.CMISPropertyAccessor;
|
|
import org.alfresco.opencmis.dictionary.DocumentTypeDefinitionWrapper;
|
|
import org.alfresco.opencmis.dictionary.PropertyDefinitionWrapper;
|
|
import org.alfresco.opencmis.dictionary.TypeDefinitionWrapper;
|
|
import org.alfresco.opencmis.mapping.DirectProperty;
|
|
import org.alfresco.opencmis.search.CMISQueryOptions;
|
|
import org.alfresco.opencmis.search.CMISQueryOptions.CMISQueryMode;
|
|
import org.alfresco.opencmis.search.CMISQueryService;
|
|
import org.alfresco.opencmis.search.CMISResultSet;
|
|
import org.alfresco.opencmis.search.CMISResultSetColumn;
|
|
import org.alfresco.opencmis.search.CMISResultSetRow;
|
|
import org.alfresco.repo.cache.SimpleCache;
|
|
import org.alfresco.repo.model.filefolder.HiddenAspect;
|
|
import org.alfresco.repo.model.filefolder.HiddenAspect.Visibility;
|
|
import org.alfresco.repo.policy.BehaviourFilter;
|
|
import org.alfresco.repo.security.authentication.AuthenticationUtil;
|
|
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
|
|
import org.alfresco.repo.security.permissions.AccessDeniedException;
|
|
import org.alfresco.repo.security.permissions.PermissionReference;
|
|
import org.alfresco.repo.security.permissions.impl.AccessPermissionImpl;
|
|
import org.alfresco.repo.security.permissions.impl.ModelDAO;
|
|
import org.alfresco.repo.tenant.TenantAdminService;
|
|
import org.alfresco.repo.tenant.TenantDeployer;
|
|
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
|
import org.alfresco.repo.version.VersionBaseModel;
|
|
import org.alfresco.repo.version.VersionModel;
|
|
import org.alfresco.service.cmr.audit.AuditQueryParameters;
|
|
import org.alfresco.service.cmr.audit.AuditService;
|
|
import org.alfresco.service.cmr.audit.AuditService.AuditQueryCallback;
|
|
import org.alfresco.service.cmr.coci.CheckOutCheckInService;
|
|
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
|
import org.alfresco.service.cmr.dictionary.InvalidAspectException;
|
|
import org.alfresco.service.cmr.lock.LockService;
|
|
import org.alfresco.service.cmr.model.FileExistsException;
|
|
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.rendition.RenditionService;
|
|
import org.alfresco.service.cmr.repository.AssociationRef;
|
|
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
|
import org.alfresco.service.cmr.repository.ContentReader;
|
|
import org.alfresco.service.cmr.repository.ContentService;
|
|
import org.alfresco.service.cmr.repository.InvalidNodeRefException;
|
|
import org.alfresco.service.cmr.repository.MimetypeService;
|
|
import org.alfresco.service.cmr.repository.NodeRef;
|
|
import org.alfresco.service.cmr.repository.NodeService;
|
|
import org.alfresco.service.cmr.repository.Path;
|
|
import org.alfresco.service.cmr.repository.Path.ChildAssocElement;
|
|
import org.alfresco.service.cmr.repository.StoreRef;
|
|
import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
|
|
import org.alfresco.service.cmr.search.SearchService;
|
|
import org.alfresco.service.cmr.security.AccessPermission;
|
|
import org.alfresco.service.cmr.security.AccessStatus;
|
|
import org.alfresco.service.cmr.security.AuthenticationService;
|
|
import org.alfresco.service.cmr.security.PermissionService;
|
|
import org.alfresco.service.cmr.site.SiteInfo;
|
|
import org.alfresco.service.cmr.site.SiteService;
|
|
import org.alfresco.service.cmr.version.VersionHistory;
|
|
import org.alfresco.service.cmr.version.VersionService;
|
|
import org.alfresco.service.cmr.version.VersionType;
|
|
import org.alfresco.service.descriptor.Descriptor;
|
|
import org.alfresco.service.descriptor.DescriptorService;
|
|
import org.alfresco.service.namespace.NamespaceService;
|
|
import org.alfresco.service.namespace.QName;
|
|
import org.alfresco.service.namespace.RegexQNamePattern;
|
|
import org.alfresco.service.transaction.TransactionService;
|
|
import org.alfresco.util.FileFilterMode;
|
|
import org.alfresco.util.FileFilterMode.Client;
|
|
import org.apache.chemistry.opencmis.commons.BasicPermissions;
|
|
import org.apache.chemistry.opencmis.commons.PropertyIds;
|
|
import org.apache.chemistry.opencmis.commons.data.Ace;
|
|
import org.apache.chemistry.opencmis.commons.data.Acl;
|
|
import org.apache.chemistry.opencmis.commons.data.AllowableActions;
|
|
import org.apache.chemistry.opencmis.commons.data.CmisExtensionElement;
|
|
import org.apache.chemistry.opencmis.commons.data.ContentStream;
|
|
import org.apache.chemistry.opencmis.commons.data.ObjectData;
|
|
import org.apache.chemistry.opencmis.commons.data.ObjectList;
|
|
import org.apache.chemistry.opencmis.commons.data.PermissionMapping;
|
|
import org.apache.chemistry.opencmis.commons.data.Properties;
|
|
import org.apache.chemistry.opencmis.commons.data.PropertyData;
|
|
import org.apache.chemistry.opencmis.commons.data.PropertyId;
|
|
import org.apache.chemistry.opencmis.commons.data.PropertyString;
|
|
import org.apache.chemistry.opencmis.commons.data.RenditionData;
|
|
import org.apache.chemistry.opencmis.commons.data.RepositoryInfo;
|
|
import org.apache.chemistry.opencmis.commons.definitions.DocumentTypeDefinition;
|
|
import org.apache.chemistry.opencmis.commons.definitions.PermissionDefinition;
|
|
import org.apache.chemistry.opencmis.commons.definitions.PropertyDefinition;
|
|
import org.apache.chemistry.opencmis.commons.enums.AclPropagation;
|
|
import org.apache.chemistry.opencmis.commons.enums.Action;
|
|
import org.apache.chemistry.opencmis.commons.enums.BaseTypeId;
|
|
import org.apache.chemistry.opencmis.commons.enums.CapabilityAcl;
|
|
import org.apache.chemistry.opencmis.commons.enums.CapabilityChanges;
|
|
import org.apache.chemistry.opencmis.commons.enums.CapabilityContentStreamUpdates;
|
|
import org.apache.chemistry.opencmis.commons.enums.CapabilityJoin;
|
|
import org.apache.chemistry.opencmis.commons.enums.CapabilityQuery;
|
|
import org.apache.chemistry.opencmis.commons.enums.CapabilityRenditions;
|
|
import org.apache.chemistry.opencmis.commons.enums.Cardinality;
|
|
import org.apache.chemistry.opencmis.commons.enums.ChangeType;
|
|
import org.apache.chemistry.opencmis.commons.enums.ContentStreamAllowed;
|
|
import org.apache.chemistry.opencmis.commons.enums.IncludeRelationships;
|
|
import org.apache.chemistry.opencmis.commons.enums.PropertyType;
|
|
import org.apache.chemistry.opencmis.commons.enums.RelationshipDirection;
|
|
import org.apache.chemistry.opencmis.commons.enums.SupportedPermissions;
|
|
import org.apache.chemistry.opencmis.commons.enums.Updatability;
|
|
import org.apache.chemistry.opencmis.commons.enums.VersioningState;
|
|
import org.apache.chemistry.opencmis.commons.exceptions.CmisBaseException;
|
|
import org.apache.chemistry.opencmis.commons.exceptions.CmisConstraintException;
|
|
import org.apache.chemistry.opencmis.commons.exceptions.CmisContentAlreadyExistsException;
|
|
import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException;
|
|
import org.apache.chemistry.opencmis.commons.exceptions.CmisObjectNotFoundException;
|
|
import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException;
|
|
import org.apache.chemistry.opencmis.commons.exceptions.CmisStreamNotSupportedException;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.AbstractPropertyData;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlEntryImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlListImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlPrincipalDataImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.AclCapabilitiesDataImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.AllowableActionsImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.ChangeEventInfoDataImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.CmisExtensionElementImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.ContentStreamImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectDataImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectListImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PermissionDefinitionDataImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PermissionMappingDataImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PolicyIdListImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertiesImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyBooleanImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyDateTimeImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyDecimalImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyHtmlImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyIdImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyIntegerImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyStringImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyUriImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.RepositoryCapabilitiesImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.RepositoryInfoImpl;
|
|
import org.apache.chemistry.opencmis.commons.server.CmisService;
|
|
import org.apache.chemistry.opencmis.commons.spi.Holder;
|
|
import org.springframework.beans.BeansException;
|
|
import org.springframework.context.ApplicationContext;
|
|
import org.springframework.context.ApplicationContextAware;
|
|
import org.springframework.context.ApplicationEvent;
|
|
import org.springframework.context.ApplicationListener;
|
|
import org.springframework.context.event.ApplicationContextEvent;
|
|
import org.springframework.extensions.surf.util.AbstractLifecycleBean;
|
|
|
|
/**
|
|
* Bridge connecting Alfresco and OpenCMIS.
|
|
* <p/>
|
|
* This class provides many of the typical services that the {@link CmisService} implementation
|
|
* will need to use Alfresco.
|
|
*
|
|
* @author florian.mueller
|
|
* @author Derek Hulley
|
|
*/
|
|
public class CMISConnector implements ApplicationContextAware, ApplicationListener<ApplicationContextEvent>, TenantDeployer
|
|
{
|
|
public static final char ID_SEPERATOR = ';';
|
|
public static final String ASSOC_ID_PREFIX = "assoc:";
|
|
public static final String PWC_VERSION_LABEL = "pwc";
|
|
public static final String UNVERSIONED_VERSION_LABEL = "1.0";
|
|
|
|
public static final String RENDITION_NONE = "cmis:none";
|
|
|
|
public static final String CMIS_CHANGELOG_AUDIT_APPLICATION = "CMISChangeLog";
|
|
|
|
public static final String ALFRESCO_EXTENSION_NAMESPACE = "http://www.alfresco.org";
|
|
public static final String CMIS_NAMESPACE = "http://docs.oasis-open.org/ns/cmis/core/200908/";
|
|
public static final String ASPECTS = "aspects";
|
|
public static final String SET_ASPECTS = "setAspects";
|
|
public static final String APPLIED_ASPECTS = "appliedAspects";
|
|
public static final String ASPECTS_TO_ADD = "aspectsToAdd";
|
|
public static final String ASPECTS_TO_REMOVE = "aspectsToRemove";
|
|
public static final String PROPERTIES = "properties";
|
|
|
|
private static final BigInteger TYPES_DEFAULT_MAX_ITEMS = BigInteger.valueOf(50);
|
|
private static final BigInteger TYPES_DEFAULT_DEPTH = BigInteger.valueOf(-1);
|
|
private static final BigInteger OBJECTS_DEFAULT_MAX_ITEMS = BigInteger.valueOf(200);
|
|
private static final BigInteger OBJECTS_DEFAULT_DEPTH = BigInteger.valueOf(10);
|
|
|
|
private static final String QUERY_NAME_OBJECT_ID = "cmis:objectId";
|
|
private static final String QUERY_NAME_OBJECT_TYPE_ID = "cmis:objectTypeId";
|
|
private static final String QUERY_NAME_BASE_TYPE_ID = "cmis:baseTypeId";
|
|
|
|
private static final String CMIS_USER = "cmis:user";
|
|
|
|
// lifecycle
|
|
private ProcessorLifecycle lifecycle = new ProcessorLifecycle();
|
|
|
|
// Alfresco objects
|
|
private DescriptorService descriptorService;
|
|
private NodeService nodeService;
|
|
private VersionService versionService;
|
|
private CheckOutCheckInService checkOutCheckInService;
|
|
private LockService lockService;
|
|
private ContentService contentService;
|
|
private RenditionService renditionService;
|
|
private FileFolderService fileFolderService;
|
|
private TenantAdminService tenantAdminService;
|
|
private TransactionService transactionService;
|
|
private AuthenticationService authenticationService;
|
|
private PermissionService permissionService;
|
|
private ModelDAO permissionModelDao;
|
|
private CMISDictionaryService cmisDictionaryService;
|
|
private CMISQueryService cmisQueryService;
|
|
private MimetypeService mimetypeService;
|
|
private AuditService auditService;
|
|
private NamespaceService namespaceService;
|
|
private SearchService searchService;
|
|
private DictionaryService dictionaryService;
|
|
private SiteService siteService;
|
|
|
|
private ActivityPoster activityPoster;
|
|
|
|
private BehaviourFilter behaviourFilter;
|
|
|
|
private HiddenAspect hiddenAspect;
|
|
|
|
private StoreRef storeRef;
|
|
private String rootPath;
|
|
private Map<String, List<String>> kindToRenditionNames;
|
|
|
|
// note: caches are tenant-aware (if using EhCacheAdapter shared cache)
|
|
|
|
private SimpleCache<String, Object> singletonCache; // eg. for cmisRootNodeRef, cmisRenditionMapping
|
|
private final String KEY_CMIS_ROOT_NODEREF = "key.cmisRoot.noderef";
|
|
private final String KEY_CMIS_RENDITION_MAPPING_NODEREF = "key.cmisRenditionMapping.noderef";
|
|
|
|
private String proxyUser;
|
|
private boolean openHttpSession = false;
|
|
|
|
// OpenCMIS objects
|
|
private BigInteger typesDefaultMaxItems = TYPES_DEFAULT_MAX_ITEMS;
|
|
private BigInteger typesDefaultDepth = TYPES_DEFAULT_DEPTH;
|
|
private BigInteger objectsDefaultMaxItems = OBJECTS_DEFAULT_MAX_ITEMS;
|
|
private BigInteger objectsDefaultDepth = OBJECTS_DEFAULT_DEPTH;
|
|
|
|
private List<PermissionDefinition> repositoryPermissions;
|
|
private Map<String, PermissionMapping> permissionMappings;
|
|
|
|
// --------------------------------------------------------------
|
|
// Configuration
|
|
// --------------------------------------------------------------
|
|
/**
|
|
* Sets the root store.
|
|
*
|
|
* @param store
|
|
* store_type://store_id
|
|
*/
|
|
public void setStore(String store)
|
|
{
|
|
this.storeRef = new StoreRef(store);
|
|
}
|
|
|
|
public void setSiteService(SiteService siteService)
|
|
{
|
|
this.siteService = siteService;
|
|
}
|
|
|
|
public void setActivityPoster(ActivityPoster activityPoster)
|
|
{
|
|
this.activityPoster = activityPoster;
|
|
}
|
|
|
|
public ActivityPoster getActivityPoster()
|
|
{
|
|
return activityPoster;
|
|
}
|
|
|
|
public void setHiddenAspect(HiddenAspect hiddenAspect)
|
|
{
|
|
this.hiddenAspect = hiddenAspect;
|
|
}
|
|
|
|
public boolean isHidden(NodeRef nodeRef)
|
|
{
|
|
final Client client = FileFilterMode.getClient();
|
|
return (hiddenAspect.getVisibility(client, nodeRef) == Visibility.NotVisible);
|
|
}
|
|
|
|
/**
|
|
* Sets the root path.
|
|
*
|
|
* @param path
|
|
* path within default store
|
|
*/
|
|
public void setRootPath(String path)
|
|
{
|
|
rootPath = path;
|
|
}
|
|
|
|
public BigInteger getTypesDefaultMaxItems()
|
|
{
|
|
return typesDefaultMaxItems;
|
|
}
|
|
|
|
public void setTypesDefaultMaxItems(BigInteger typesDefaultMaxItems)
|
|
{
|
|
this.typesDefaultMaxItems = typesDefaultMaxItems;
|
|
}
|
|
|
|
public BigInteger getTypesDefaultDepth()
|
|
{
|
|
return typesDefaultDepth;
|
|
}
|
|
|
|
public void setTypesDefaultDepth(BigInteger typesDefaultDepth)
|
|
{
|
|
this.typesDefaultDepth = typesDefaultDepth;
|
|
}
|
|
|
|
public BigInteger getObjectsDefaultMaxItems()
|
|
{
|
|
return objectsDefaultMaxItems;
|
|
}
|
|
|
|
public void setObjectsDefaultMaxItems(BigInteger objectsDefaultMaxItems)
|
|
{
|
|
this.objectsDefaultMaxItems = objectsDefaultMaxItems;
|
|
}
|
|
|
|
public BigInteger getObjectsDefaultDepth()
|
|
{
|
|
return objectsDefaultDepth;
|
|
}
|
|
|
|
public void setObjectsDefaultDepth(BigInteger objectsDefaultDepth)
|
|
{
|
|
this.objectsDefaultDepth = objectsDefaultDepth;
|
|
}
|
|
|
|
/**
|
|
* Set rendition kind mapping.
|
|
*/
|
|
public void setRenditionKindMapping(Map<String, List<String>> renditionKinds)
|
|
{
|
|
this.kindToRenditionNames = renditionKinds;
|
|
}
|
|
|
|
public void setOpenHttpSession(boolean openHttpSession)
|
|
{
|
|
this.openHttpSession = openHttpSession;
|
|
}
|
|
|
|
public boolean openHttpSession()
|
|
{
|
|
return openHttpSession;
|
|
}
|
|
|
|
/**
|
|
* Sets the descriptor service.
|
|
*/
|
|
public void setDescriptorService(DescriptorService descriptorService)
|
|
{
|
|
this.descriptorService = descriptorService;
|
|
}
|
|
|
|
public DescriptorService getDescriptorService()
|
|
{
|
|
return descriptorService;
|
|
}
|
|
|
|
public void setBehaviourFilter(BehaviourFilter behaviourFilter)
|
|
{
|
|
this.behaviourFilter = behaviourFilter;
|
|
}
|
|
|
|
/**
|
|
* Sets the node service.
|
|
*/
|
|
public void setNodeService(NodeService nodeService)
|
|
{
|
|
this.nodeService = nodeService;
|
|
}
|
|
|
|
public NodeService getNodeService()
|
|
{
|
|
return nodeService;
|
|
}
|
|
|
|
/**
|
|
* Sets the version service.
|
|
*/
|
|
public void setVersionService(VersionService versionService)
|
|
{
|
|
this.versionService = versionService;
|
|
}
|
|
|
|
public VersionService getVersionService()
|
|
{
|
|
return versionService;
|
|
}
|
|
|
|
/**
|
|
* Sets the checkOut/checkIn service.
|
|
*/
|
|
public void setCheckOutCheckInService(CheckOutCheckInService checkOutCheckInService)
|
|
{
|
|
this.checkOutCheckInService = checkOutCheckInService;
|
|
}
|
|
|
|
public CheckOutCheckInService getCheckOutCheckInService()
|
|
{
|
|
return checkOutCheckInService;
|
|
}
|
|
|
|
/**
|
|
* Sets the lock service.
|
|
*/
|
|
public LockService getLockService()
|
|
{
|
|
return lockService;
|
|
}
|
|
|
|
public void setLockService(LockService lockService)
|
|
{
|
|
this.lockService = lockService;
|
|
}
|
|
|
|
/**
|
|
* Sets the content service.
|
|
*/
|
|
public void setContentService(ContentService contentService)
|
|
{
|
|
this.contentService = contentService;
|
|
}
|
|
|
|
public ContentService getContentService()
|
|
{
|
|
return contentService;
|
|
}
|
|
|
|
/**
|
|
* Sets the rendition service.
|
|
*/
|
|
public void setrenditionService(RenditionService renditionService)
|
|
{
|
|
this.renditionService = renditionService;
|
|
}
|
|
|
|
/**
|
|
* Sets the file folder service.
|
|
*/
|
|
public void setFileFolderService(FileFolderService fileFolderService)
|
|
{
|
|
this.fileFolderService = fileFolderService;
|
|
}
|
|
|
|
public FileFolderService getFileFolderService()
|
|
{
|
|
return fileFolderService;
|
|
}
|
|
|
|
/**
|
|
* Sets the tenant admin service.
|
|
*/
|
|
public void setTenantAdminService(TenantAdminService tenantAdminService)
|
|
{
|
|
this.tenantAdminService = tenantAdminService;
|
|
}
|
|
|
|
public void setSingletonCache(SimpleCache<String, Object> singletonCache)
|
|
{
|
|
this.singletonCache = singletonCache;
|
|
}
|
|
|
|
/**
|
|
* Sets the transaction service.
|
|
*/
|
|
public void setTransactionService(TransactionService transactionService)
|
|
{
|
|
this.transactionService = transactionService;
|
|
}
|
|
|
|
public TransactionService getTransactionService()
|
|
{
|
|
return transactionService;
|
|
}
|
|
|
|
/**
|
|
* Sets the authentication service.
|
|
*/
|
|
public void setAuthenticationService(AuthenticationService authenticationService)
|
|
{
|
|
this.authenticationService = authenticationService;
|
|
}
|
|
|
|
public AuthenticationService getAuthenticationService()
|
|
{
|
|
return authenticationService;
|
|
}
|
|
|
|
/**
|
|
* Sets the permission service.
|
|
*/
|
|
public void setPermissionService(PermissionService permissionService)
|
|
{
|
|
this.permissionService = permissionService;
|
|
}
|
|
|
|
/**
|
|
* Sets the permission model DAO.
|
|
*/
|
|
public void setPermissionModelDao(ModelDAO permissionModelDao)
|
|
{
|
|
this.permissionModelDao = permissionModelDao;
|
|
}
|
|
|
|
public void setOpenCMISDictionaryService(CMISDictionaryService cmisDictionaryService)
|
|
{
|
|
this.cmisDictionaryService = cmisDictionaryService;
|
|
}
|
|
|
|
public CMISDictionaryService getOpenCMISDictionaryService()
|
|
{
|
|
return cmisDictionaryService;
|
|
}
|
|
|
|
/**
|
|
* Sets the OpenCMIS query service.
|
|
*/
|
|
public void setOpenCMISQueryService(CMISQueryService cmisQueryService)
|
|
{
|
|
this.cmisQueryService = cmisQueryService;
|
|
}
|
|
|
|
/**
|
|
* Sets the MIME type service.
|
|
*/
|
|
public void setMimetypeService(MimetypeService mimetypeService)
|
|
{
|
|
this.mimetypeService = mimetypeService;
|
|
}
|
|
|
|
public MimetypeService getMimetypeService()
|
|
{
|
|
return mimetypeService;
|
|
}
|
|
|
|
/**
|
|
* Sets the audit service.
|
|
*/
|
|
public void setAuditService(AuditService auditService)
|
|
{
|
|
this.auditService = auditService;
|
|
}
|
|
|
|
/**
|
|
* Sets the namespace service.
|
|
*/
|
|
public void setNamespaceService(NamespaceService namespaceService)
|
|
{
|
|
this.namespaceService = namespaceService;
|
|
}
|
|
|
|
/**
|
|
* Sets the search service.
|
|
*/
|
|
public void setSearchService(SearchService searchService)
|
|
{
|
|
this.searchService = searchService;
|
|
}
|
|
|
|
public SearchService getSearchService()
|
|
{
|
|
return searchService;
|
|
}
|
|
|
|
public void setDictionaryService(DictionaryService dictionaryService)
|
|
{
|
|
this.dictionaryService = dictionaryService;
|
|
}
|
|
|
|
public DictionaryService getDictionaryService()
|
|
{
|
|
return dictionaryService;
|
|
}
|
|
|
|
/**
|
|
* Not implemented
|
|
* @throws UnsupportedOperationException always
|
|
*/
|
|
public void setProxyUser(String proxyUser)
|
|
{
|
|
// this.proxyUser = proxyUser;
|
|
throw new UnsupportedOperationException("proxyUser setting not implemented. Please raise a JIRA request.");
|
|
}
|
|
|
|
public String getProxyUser()
|
|
{
|
|
return proxyUser;
|
|
}
|
|
|
|
// --------------------------------------------------------------
|
|
// Lifecycle methods
|
|
// --------------------------------------------------------------
|
|
|
|
public void init()
|
|
{
|
|
// register as tenant deployer
|
|
tenantAdminService.register(this);
|
|
|
|
// set up and cache rendition mapping
|
|
getRenditionMapping();
|
|
|
|
// cache root node ref
|
|
getRootNodeRef();
|
|
|
|
// cache permission definitions and permission mappings
|
|
repositoryPermissions = getRepositoryPermissions();
|
|
permissionMappings = getPermissionMappings();
|
|
}
|
|
|
|
public void destroy()
|
|
{
|
|
singletonCache.remove(KEY_CMIS_ROOT_NODEREF);
|
|
singletonCache.remove(KEY_CMIS_RENDITION_MAPPING_NODEREF);
|
|
}
|
|
|
|
public void onEnableTenant()
|
|
{
|
|
init();
|
|
}
|
|
|
|
public void onDisableTenant()
|
|
{
|
|
destroy();
|
|
}
|
|
|
|
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
|
|
{
|
|
lifecycle.setApplicationContext(applicationContext);
|
|
}
|
|
|
|
public void onApplicationEvent(ApplicationContextEvent event)
|
|
{
|
|
lifecycle.onApplicationEvent(event);
|
|
}
|
|
|
|
/**
|
|
* Hooks into Spring Application Lifecycle.
|
|
*/
|
|
private class ProcessorLifecycle extends AbstractLifecycleBean
|
|
{
|
|
@Override
|
|
protected void onBootstrap(ApplicationEvent event)
|
|
{
|
|
init();
|
|
}
|
|
|
|
@Override
|
|
protected void onShutdown(ApplicationEvent event)
|
|
{
|
|
}
|
|
}
|
|
|
|
// --------------------------------------------------------------
|
|
// Alfresco methods
|
|
// --------------------------------------------------------------
|
|
|
|
public SiteInfo getSite(NodeRef nodeRef)
|
|
{
|
|
return siteService.getSite(nodeRef);
|
|
}
|
|
|
|
public boolean disableBehaviour(QName className, NodeRef nodeRef)
|
|
{
|
|
boolean wasEnabled = behaviourFilter.isEnabled(nodeRef, className);
|
|
if(wasEnabled)
|
|
{
|
|
behaviourFilter.disableBehaviour(nodeRef, className);
|
|
}
|
|
return wasEnabled;
|
|
}
|
|
|
|
public boolean enableBehaviour(QName className, NodeRef nodeRef)
|
|
{
|
|
boolean isEnabled = behaviourFilter.isEnabled(nodeRef, className);
|
|
if(!isEnabled)
|
|
{
|
|
behaviourFilter.enableBehaviour(nodeRef, className);
|
|
}
|
|
return isEnabled;
|
|
}
|
|
|
|
public StoreRef getRootStoreRef()
|
|
{
|
|
return getRootNodeRef().getStoreRef();
|
|
}
|
|
|
|
public void deleteNode(NodeRef nodeRef, boolean postActivity)
|
|
{
|
|
ActivityInfo activityInfo = null;
|
|
|
|
// post activity after removal of the node
|
|
postActivity &= hiddenAspect.getVisibility(Client.cmis, nodeRef) == Visibility.Visible;
|
|
if(postActivity)
|
|
{
|
|
// get this information before the node is deleted
|
|
activityInfo = activityPoster.getActivityInfo(nodeRef);
|
|
}
|
|
|
|
getNodeService().deleteNode(nodeRef);
|
|
|
|
// post activity after removal of the node
|
|
if(postActivity && activityInfo != null)
|
|
{
|
|
activityPoster.postFileFolderDeleted(activityInfo);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the root folder node ref.
|
|
*/
|
|
public NodeRef getRootNodeRef()
|
|
{
|
|
NodeRef rootNodeRef = (NodeRef)singletonCache.get(KEY_CMIS_ROOT_NODEREF);
|
|
if (rootNodeRef == null)
|
|
{
|
|
rootNodeRef = AuthenticationUtil.runAs(new RunAsWork<NodeRef>()
|
|
{
|
|
public NodeRef doWork() throws Exception
|
|
{
|
|
return transactionService.getRetryingTransactionHelper().doInTransaction(
|
|
new RetryingTransactionCallback<NodeRef>()
|
|
{
|
|
public NodeRef execute() throws Exception
|
|
{
|
|
NodeRef root = nodeService.getRootNode(storeRef);
|
|
List<NodeRef> rootNodes = searchService.selectNodes(root, rootPath, null,
|
|
namespaceService, false);
|
|
if (rootNodes.size() != 1)
|
|
{
|
|
throw new CmisRuntimeException("Unable to locate CMIS root path " + rootPath);
|
|
}
|
|
return rootNodes.get(0);
|
|
};
|
|
}, true);
|
|
}
|
|
}, AuthenticationUtil.getSystemUserName());
|
|
|
|
if (rootNodeRef == null)
|
|
{
|
|
throw new CmisObjectNotFoundException("Root folder path '" + rootPath + "' not found!");
|
|
}
|
|
|
|
singletonCache.put(KEY_CMIS_ROOT_NODEREF, rootNodeRef);
|
|
}
|
|
|
|
return rootNodeRef;
|
|
}
|
|
|
|
public String getName(NodeRef nodeRef)
|
|
{
|
|
try
|
|
{
|
|
Object name = nodeService.getProperty(nodeRef, ContentModel.PROP_NAME);
|
|
return (name instanceof String ? (String) name : null);
|
|
}
|
|
catch(InvalidNodeRefException inre)
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cuts of the version information from an object id.
|
|
*/
|
|
public String getCurrentVersionId(String objectId)
|
|
{
|
|
if (objectId == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
int sepIndex = objectId.lastIndexOf(ID_SEPERATOR);
|
|
if (sepIndex > -1)
|
|
{
|
|
return objectId.substring(0, sepIndex);
|
|
}
|
|
|
|
return objectId;
|
|
}
|
|
|
|
/**
|
|
* Creates an object info object.
|
|
*/
|
|
public CMISNodeInfoImpl createNodeInfo(String objectId)
|
|
{
|
|
return new CMISNodeInfoImpl(this, objectId);
|
|
}
|
|
|
|
/**
|
|
* Creates an object info object.
|
|
*/
|
|
public CMISNodeInfoImpl createNodeInfo(NodeRef nodeRef)
|
|
{
|
|
return createNodeInfo(nodeRef, null);
|
|
}
|
|
|
|
/**
|
|
* Creates an object info object.
|
|
*/
|
|
public CMISNodeInfoImpl createNodeInfo(NodeRef nodeRef, VersionHistory versionHistory)
|
|
{
|
|
return new CMISNodeInfoImpl(this, nodeRef, versionHistory);
|
|
}
|
|
|
|
/**
|
|
* Creates an object info object.
|
|
*/
|
|
public CMISNodeInfoImpl createNodeInfo(AssociationRef assocRef)
|
|
{
|
|
return new CMISNodeInfoImpl(this, assocRef);
|
|
}
|
|
|
|
/**
|
|
* Compiles a CMIS object if for a live node.
|
|
*/
|
|
public String createObjectId(NodeRef currentVersionNodeRef)
|
|
{
|
|
if(getFileFolderService().getFileInfo(currentVersionNodeRef).isFolder())
|
|
{
|
|
return currentVersionNodeRef.toString();
|
|
}
|
|
|
|
Serializable versionLabel = getNodeService()
|
|
.getProperty(currentVersionNodeRef, ContentModel.PROP_VERSION_LABEL);
|
|
if (versionLabel == null)
|
|
{
|
|
versionLabel = CMISConnector.UNVERSIONED_VERSION_LABEL;
|
|
}
|
|
|
|
return currentVersionNodeRef.toString() + CMISConnector.ID_SEPERATOR + versionLabel;
|
|
}
|
|
|
|
/**
|
|
* Returns the type definition of a node or <code>null</code> if no type
|
|
* definition could be found.
|
|
*/
|
|
public TypeDefinitionWrapper getType(NodeRef nodeRef)
|
|
{
|
|
try
|
|
{
|
|
QName typeQName = nodeService.getType(nodeRef);
|
|
return getType(typeQName);
|
|
}
|
|
catch(InvalidNodeRefException inre)
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private TypeDefinitionWrapper getType(QName typeQName)
|
|
{
|
|
return cmisDictionaryService.findNodeType(typeQName);
|
|
}
|
|
|
|
/**
|
|
* Returns the type definition of an association or <code>null</code> if no
|
|
* type definition could be found.
|
|
*/
|
|
public TypeDefinitionWrapper getType(AssociationRef assocRef)
|
|
{
|
|
QName typeQName = assocRef.getTypeQName();
|
|
return cmisDictionaryService.findAssocType(typeQName);
|
|
}
|
|
|
|
/**
|
|
* Returns the type definition of a node or <code>null</code> if no type
|
|
* definition could be found.
|
|
*/
|
|
public TypeDefinitionWrapper getType(String cmisTypeId)
|
|
{
|
|
return cmisDictionaryService.findType(cmisTypeId);
|
|
}
|
|
|
|
/**
|
|
* Returns the definition after it has checked if the type can be used for
|
|
* object creation.
|
|
*/
|
|
public TypeDefinitionWrapper getTypeForCreate(String cmisTypeId, BaseTypeId baseTypeId)
|
|
{
|
|
TypeDefinitionWrapper type = getType(cmisTypeId);
|
|
if ((type == null) || (type.getBaseTypeId() != baseTypeId))
|
|
{
|
|
switch (baseTypeId)
|
|
{
|
|
case CMIS_DOCUMENT:
|
|
throw new CmisConstraintException("Type is not a document type!");
|
|
case CMIS_FOLDER:
|
|
throw new CmisConstraintException("Type is not a folder type!");
|
|
case CMIS_RELATIONSHIP:
|
|
throw new CmisConstraintException("Type is not a relationship type!");
|
|
case CMIS_POLICY:
|
|
throw new CmisConstraintException("Type is not a policy type!");
|
|
}
|
|
}
|
|
|
|
if (!type.getTypeDefinition(false).isCreatable())
|
|
{
|
|
throw new CmisConstraintException("Type is not creatable!");
|
|
}
|
|
|
|
return type;
|
|
}
|
|
|
|
/**
|
|
* Applies a versioning state to a document.
|
|
*/
|
|
public void applyVersioningState(NodeRef nodeRef, VersioningState versioningState)
|
|
{
|
|
if (versioningState == VersioningState.CHECKEDOUT)
|
|
{
|
|
if (!nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE))
|
|
{
|
|
Map<QName, Serializable> props = new HashMap<QName, Serializable>();
|
|
props.put(ContentModel.PROP_INITIAL_VERSION, false);
|
|
props.put(ContentModel.PROP_AUTO_VERSION, false);
|
|
nodeService.addAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE, props);
|
|
}
|
|
getCheckOutCheckInService().checkout(nodeRef);
|
|
}
|
|
else if ((versioningState == VersioningState.MAJOR) || (versioningState == VersioningState.MINOR))
|
|
{
|
|
if (!nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE))
|
|
{
|
|
Map<QName, Serializable> props = new HashMap<QName, Serializable>();
|
|
props.put(ContentModel.PROP_INITIAL_VERSION, false);
|
|
props.put(ContentModel.PROP_AUTO_VERSION, false);
|
|
nodeService.addAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE, props);
|
|
}
|
|
|
|
Map<String, Serializable> versionProperties = new HashMap<String, Serializable>(5);
|
|
versionProperties.put(
|
|
VersionModel.PROP_VERSION_TYPE,
|
|
versioningState == VersioningState.MAJOR ? VersionType.MAJOR : VersionType.MINOR);
|
|
versionProperties.put(VersionModel.PROP_DESCRIPTION, "Initial Version");
|
|
|
|
versionService.createVersion(nodeRef, versionProperties);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks if a child of a given type can be added to a given folder.
|
|
*/
|
|
@SuppressWarnings("unchecked")
|
|
public void checkChildObjectType(CMISNodeInfo folderInfo, String childType)
|
|
{
|
|
TypeDefinitionWrapper targetType = folderInfo.getType();
|
|
PropertyDefinitionWrapper allowableChildObjectTypeProperty = targetType
|
|
.getPropertyById(PropertyIds.ALLOWED_CHILD_OBJECT_TYPE_IDS);
|
|
List<String> childTypes = (List<String>) allowableChildObjectTypeProperty.getPropertyAccessor().getValue(
|
|
folderInfo);
|
|
|
|
if ((childTypes == null) || childTypes.isEmpty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!childTypes.contains(childType))
|
|
{
|
|
throw new CmisConstraintException("Objects of type '" + childType + "' cannot be added to this folder!");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates the CMIS object for a node.
|
|
*/
|
|
public ObjectData createCMISObject(CMISNodeInfo info, FileInfo node, String filter,
|
|
boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter,
|
|
boolean includePolicyIds, boolean includeAcl)
|
|
{
|
|
if (info.getType() == null)
|
|
{
|
|
throw new CmisObjectNotFoundException("No corresponding type found! Not a CMIS object?");
|
|
}
|
|
|
|
Properties nodeProps = getNodeProperties(info, node, filter, info.getType());
|
|
|
|
return createCMISObjectImpl(info, nodeProps, filter, includeAllowableActions, includeRelationships,
|
|
renditionFilter, includePolicyIds, includeAcl);
|
|
}
|
|
|
|
public ObjectData createCMISObject(CMISNodeInfo info, String filter, boolean includeAllowableActions,
|
|
IncludeRelationships includeRelationships, String renditionFilter, boolean includePolicyIds,
|
|
boolean includeAcl)
|
|
{
|
|
if (info.getType() == null)
|
|
{
|
|
throw new CmisObjectNotFoundException("No corresponding type found! Not a CMIS object?");
|
|
}
|
|
|
|
Properties nodeProps = (info.isRelationship() ? getAssocProperties(info, filter) : getNodeProperties(info,
|
|
filter));
|
|
|
|
return createCMISObjectImpl(info, nodeProps, filter, includeAllowableActions, includeRelationships,
|
|
renditionFilter, includePolicyIds, includeAcl);
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
private ObjectData createCMISObjectImpl(CMISNodeInfo info, Properties nodeProps, String filter,
|
|
boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter,
|
|
boolean includePolicyIds, boolean includeAcl)
|
|
{
|
|
ObjectDataImpl result = new ObjectDataImpl();
|
|
|
|
// set allowable actions
|
|
if (includeAllowableActions)
|
|
{
|
|
result.setAllowableActions(getAllowableActions(info));
|
|
}
|
|
|
|
// set policy ids
|
|
if (includePolicyIds)
|
|
{
|
|
result.setPolicyIds(new PolicyIdListImpl());
|
|
}
|
|
|
|
if (info.isRelationship())
|
|
{
|
|
// set properties
|
|
result.setProperties(getAssocProperties(info, filter));
|
|
|
|
// set ACL
|
|
if (includeAcl)
|
|
{
|
|
// association have no ACL - return an empty list of ACEs
|
|
result.setAcl(new AccessControlListImpl((List<Ace>) Collections.EMPTY_LIST));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// set properties
|
|
result.setProperties(nodeProps);
|
|
|
|
// set relationships
|
|
if (includeRelationships != IncludeRelationships.NONE)
|
|
{
|
|
result.setRelationships(getRelationships(info.getNodeRef(), includeRelationships));
|
|
}
|
|
|
|
// set renditions
|
|
if (!RENDITION_NONE.equals(renditionFilter))
|
|
{
|
|
List<RenditionData> renditions = getRenditions(info.getNodeRef(), renditionFilter, null, null);
|
|
if ((renditions != null) && (!renditions.isEmpty()))
|
|
{
|
|
result.setRenditions(renditions);
|
|
}
|
|
}
|
|
|
|
// set ACL
|
|
if (includeAcl)
|
|
{
|
|
result.setAcl(getACL(info.getCurrentNodeNodeRef(), false));
|
|
}
|
|
|
|
// add aspects
|
|
List<CmisExtensionElement> extensions = getAspectExtensions(info, filter, result.getProperties()
|
|
.getProperties().keySet());
|
|
if (!extensions.isEmpty())
|
|
{
|
|
result.getProperties().setExtensions(
|
|
Collections.singletonList((CmisExtensionElement) new CmisExtensionElementImpl(
|
|
ALFRESCO_EXTENSION_NAMESPACE, ASPECTS, null, extensions)));
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public String getPath(NodeRef nodeRef)
|
|
{
|
|
try
|
|
{
|
|
Path path = nodeService.getPath(nodeRef);
|
|
|
|
// skip to CMIS root path
|
|
NodeRef rootNode = getRootNodeRef();
|
|
int i = 0;
|
|
while (i < path.size())
|
|
{
|
|
Path.Element element = path.get(i);
|
|
if (element instanceof ChildAssocElement)
|
|
{
|
|
ChildAssociationRef assocRef = ((ChildAssocElement) element).getRef();
|
|
NodeRef node = assocRef.getChildRef();
|
|
if (node.equals(rootNode))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
i++;
|
|
}
|
|
|
|
StringBuilder displayPath = new StringBuilder(64);
|
|
|
|
if (path.size() - i == 1)
|
|
{
|
|
// render root path
|
|
displayPath.append("/");
|
|
}
|
|
else
|
|
{
|
|
// render CMIS scoped path
|
|
i++;
|
|
while (i < path.size())
|
|
{
|
|
Path.Element element = path.get(i);
|
|
if (element instanceof ChildAssocElement)
|
|
{
|
|
ChildAssociationRef assocRef = ((ChildAssocElement) element).getRef();
|
|
NodeRef node = assocRef.getChildRef();
|
|
displayPath.append("/");
|
|
displayPath.append(nodeService.getProperty(node, ContentModel.PROP_NAME));
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
|
|
return displayPath.toString();
|
|
}
|
|
catch(InvalidNodeRefException inre)
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the content from the repository.
|
|
*/
|
|
public ContentStream getContentStream(CMISNodeInfo info, String streamId, BigInteger offset, BigInteger length)
|
|
{
|
|
// get the type and check if the object can have content
|
|
TypeDefinitionWrapper type = info.getType();
|
|
checkDocumentTypeForContent(type);
|
|
|
|
// looks like a document, now get the content
|
|
ContentStreamImpl result = new ContentStreamImpl();
|
|
result.setFileName(info.getName());
|
|
|
|
// if streamId is set, fetch other content
|
|
NodeRef streamNodeRef = info.getNodeRef();
|
|
if ((streamId != null) && (streamId.length() > 0))
|
|
{
|
|
CMISNodeInfo streamInfo = createNodeInfo(streamId);
|
|
if (!streamInfo.isVariant(CMISObjectVariant.CURRENT_VERSION))
|
|
{
|
|
throw new CmisInvalidArgumentException("Stream id is invalid: " + streamId);
|
|
}
|
|
|
|
streamNodeRef = streamInfo.getNodeRef();
|
|
type = streamInfo.getType();
|
|
checkDocumentTypeForContent(type);
|
|
}
|
|
|
|
// get the stream now
|
|
try
|
|
{
|
|
ContentReader contentReader = contentService.getReader(streamNodeRef, ContentModel.PROP_CONTENT);
|
|
if (contentReader == null)
|
|
{
|
|
throw new CmisConstraintException("Document has no content!");
|
|
}
|
|
|
|
result.setMimeType(contentReader.getMimetype());
|
|
|
|
if ((offset == null) && (length == null))
|
|
{
|
|
result.setStream(contentReader.getContentInputStream());
|
|
result.setLength(BigInteger.valueOf(contentReader.getSize()));
|
|
}
|
|
else
|
|
{
|
|
long off = (offset == null ? 0 : offset.longValue());
|
|
long len = (length == null ? contentReader.getSize() : length.longValue());
|
|
if (off + len > contentReader.getSize())
|
|
{
|
|
len = contentReader.getSize() - off;
|
|
}
|
|
|
|
result.setStream(new RangeInputStream(contentReader.getContentInputStream(), off, len));
|
|
result.setLength(BigInteger.valueOf(len));
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
if (e instanceof CmisBaseException)
|
|
{
|
|
throw (CmisBaseException) e;
|
|
}
|
|
else
|
|
{
|
|
throw new CmisRuntimeException("Failed to retrieve content: " + e.getMessage(), e);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private void checkDocumentTypeForContent(TypeDefinitionWrapper type)
|
|
{
|
|
if (type == null)
|
|
{
|
|
throw new CmisObjectNotFoundException("No corresponding type found! Not a CMIS object?");
|
|
}
|
|
if (!(type instanceof DocumentTypeDefinitionWrapper))
|
|
{
|
|
throw new CmisStreamNotSupportedException("Object is not a docuemnt!");
|
|
}
|
|
if (((DocumentTypeDefinition) type.getTypeDefinition(false)).getContentStreamAllowed() == ContentStreamAllowed.NOTALLOWED)
|
|
{
|
|
throw new CmisConstraintException("Document cannot have content!");
|
|
}
|
|
}
|
|
|
|
public Properties getNodeProperties(CMISNodeInfo info, String filter)
|
|
{
|
|
PropertiesImpl result = new PropertiesImpl();
|
|
|
|
Set<String> filterSet = splitFilter(filter);
|
|
|
|
for (PropertyDefinitionWrapper propDef : info.getType().getProperties())
|
|
{
|
|
if (!propDef.getPropertyId().equals(PropertyIds.OBJECT_ID))
|
|
{
|
|
// don't filter the object id
|
|
if ((filterSet != null) && (!filterSet.contains(propDef.getPropertyDefinition().getQueryName())))
|
|
{
|
|
// skip properties that are not in the filter
|
|
continue;
|
|
}
|
|
}
|
|
|
|
Serializable value = propDef.getPropertyAccessor().getValue(info);
|
|
result.addProperty(getProperty(propDef.getPropertyDefinition().getPropertyType(), propDef, value));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public Properties getNodeProperties(CMISNodeInfo info, FileInfo node, String filter, TypeDefinitionWrapper type)
|
|
{
|
|
PropertiesImpl result = new PropertiesImpl();
|
|
|
|
Set<String> filterSet = splitFilter(filter);
|
|
|
|
Map<QName, Serializable> nodeProps = node.getProperties();
|
|
|
|
for (PropertyDefinitionWrapper propDef : type.getProperties())
|
|
{
|
|
if (!propDef.getPropertyId().equals(PropertyIds.OBJECT_ID))
|
|
{
|
|
// don't filter the object id
|
|
if ((filterSet != null) && (!filterSet.contains(propDef.getPropertyDefinition().getQueryName())))
|
|
{
|
|
// skip properties that are not in the filter
|
|
continue;
|
|
}
|
|
}
|
|
|
|
Serializable value = null;
|
|
|
|
CMISPropertyAccessor accessor = propDef.getPropertyAccessor();
|
|
if (accessor instanceof DirectProperty)
|
|
{
|
|
value = nodeProps.get(accessor.getMappedProperty());
|
|
}
|
|
else
|
|
{
|
|
value = propDef.getPropertyAccessor().getValue(info);
|
|
}
|
|
|
|
result.addProperty(getProperty(propDef.getPropertyDefinition().getPropertyType(), propDef, value));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public Properties getAssocProperties(CMISNodeInfo info, String filter)
|
|
{
|
|
PropertiesImpl result = new PropertiesImpl();
|
|
|
|
Set<String> filterSet = splitFilter(filter);
|
|
|
|
for (PropertyDefinitionWrapper propDefWrap : info.getType().getProperties())
|
|
{
|
|
PropertyDefinition<?> propDef = propDefWrap.getPropertyDefinition();
|
|
if ((filterSet != null) && (!filterSet.contains(propDef.getQueryName())))
|
|
{
|
|
// skip properties that are not in the filter
|
|
continue;
|
|
}
|
|
|
|
CMISPropertyAccessor cmisPropertyAccessor = propDefWrap.getPropertyAccessor();
|
|
Serializable value = cmisPropertyAccessor.getValue(info);
|
|
PropertyType propType = propDef.getPropertyType();
|
|
PropertyData<?> propertyData = getProperty(propType, propDefWrap, value);
|
|
result.addProperty(propertyData);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Builds aspect extension.
|
|
*/
|
|
public List<CmisExtensionElement> getAspectExtensions(CMISNodeInfo info, String filter,
|
|
Set<String> alreadySetProperties)
|
|
{
|
|
List<CmisExtensionElement> extensions = new ArrayList<CmisExtensionElement>();
|
|
Set<String> propertyIds = new HashSet<String>(alreadySetProperties);
|
|
Set<String> filterSet = splitFilter(filter);
|
|
|
|
Set<QName> aspects = nodeService.getAspects(info.getNodeRef());
|
|
for (QName aspect : aspects)
|
|
{
|
|
TypeDefinitionWrapper aspectType = cmisDictionaryService.findNodeType(aspect);
|
|
if (aspectType == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
extensions.add(new CmisExtensionElementImpl(ALFRESCO_EXTENSION_NAMESPACE, APPLIED_ASPECTS, null, aspectType
|
|
.getTypeId()));
|
|
|
|
List<CmisExtensionElement> propertyExtensionList = new ArrayList<CmisExtensionElement>();
|
|
for (PropertyDefinitionWrapper propDef : aspectType.getProperties())
|
|
{
|
|
if (propertyIds.contains(propDef.getPropertyId()))
|
|
{
|
|
// skip properties that have already been added
|
|
continue;
|
|
}
|
|
|
|
if ((filterSet != null) && (!filterSet.contains(propDef.getPropertyDefinition().getQueryName())))
|
|
{
|
|
// skip properties that are not in the filter
|
|
continue;
|
|
}
|
|
|
|
Serializable value = propDef.getPropertyAccessor().getValue(info);
|
|
propertyExtensionList.add(createAspectPropertyExtension(propDef.getPropertyDefinition(), value));
|
|
|
|
// mark property as 'added'
|
|
propertyIds.add(propDef.getPropertyId());
|
|
}
|
|
|
|
if (!propertyExtensionList.isEmpty())
|
|
{
|
|
CmisExtensionElementImpl propertiesExtension = new CmisExtensionElementImpl(
|
|
ALFRESCO_EXTENSION_NAMESPACE, "properties", null, propertyExtensionList);
|
|
extensions.addAll(Collections.singletonList(propertiesExtension));
|
|
}
|
|
}
|
|
|
|
return extensions;
|
|
}
|
|
|
|
/**
|
|
* Creates a property extension element.
|
|
*/
|
|
@SuppressWarnings("rawtypes")
|
|
private CmisExtensionElement createAspectPropertyExtension(PropertyDefinition<?> propertyDefintion, Object value)
|
|
{
|
|
String name;
|
|
switch (propertyDefintion.getPropertyType())
|
|
{
|
|
case BOOLEAN:
|
|
name = "propertyBoolean";
|
|
break;
|
|
case DATETIME:
|
|
name = "propertyDateTime";
|
|
break;
|
|
case DECIMAL:
|
|
name = "propertyDecimal";
|
|
break;
|
|
case INTEGER:
|
|
name = "propertyInteger";
|
|
break;
|
|
case ID:
|
|
name = "propertyId";
|
|
break;
|
|
default:
|
|
name = "propertyString";
|
|
}
|
|
|
|
Map<String, String> attributes = new HashMap<String, String>();
|
|
attributes.put("propertyDefinitionId", propertyDefintion.getId());
|
|
|
|
List<CmisExtensionElement> propertyValues = new ArrayList<CmisExtensionElement>();
|
|
if (value != null)
|
|
{
|
|
if (value instanceof List)
|
|
{
|
|
for (Object o : ((List) value))
|
|
{
|
|
propertyValues.add(new CmisExtensionElementImpl(CMIS_NAMESPACE, "value", null,
|
|
convertAspectPropertyValue(o)));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
propertyValues.add(new CmisExtensionElementImpl(CMIS_NAMESPACE, "value", null,
|
|
convertAspectPropertyValue(value)));
|
|
}
|
|
}
|
|
|
|
return new CmisExtensionElementImpl(CMIS_NAMESPACE, name, attributes, propertyValues);
|
|
}
|
|
|
|
private String convertAspectPropertyValue(Object value)
|
|
{
|
|
if (value instanceof Date)
|
|
{
|
|
GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
|
|
cal.setTime((Date) value);
|
|
value = cal;
|
|
}
|
|
|
|
if (value instanceof GregorianCalendar)
|
|
{
|
|
DatatypeFactory df;
|
|
try
|
|
{
|
|
df = DatatypeFactory.newInstance();
|
|
}
|
|
catch (DatatypeConfigurationException e)
|
|
{
|
|
throw new IllegalArgumentException("Aspect conversation exception: " + e.getMessage(), e);
|
|
}
|
|
return df.newXMLGregorianCalendar((GregorianCalendar) value).toXMLFormat();
|
|
}
|
|
|
|
return value.toString();
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
private AbstractPropertyData<?> getProperty(PropertyType propType, PropertyDefinitionWrapper propDef,
|
|
Serializable value)
|
|
{
|
|
AbstractPropertyData<?> result = null;
|
|
switch (propType)
|
|
{
|
|
case BOOLEAN:
|
|
result = new PropertyBooleanImpl();
|
|
if (value instanceof List)
|
|
{
|
|
((PropertyBooleanImpl) result).setValues((List<Boolean>) value);
|
|
}
|
|
else
|
|
{
|
|
((PropertyBooleanImpl) result).setValue((Boolean) value);
|
|
}
|
|
break;
|
|
case DATETIME:
|
|
result = new PropertyDateTimeImpl();
|
|
if (value instanceof List)
|
|
{
|
|
((PropertyDateTimeImpl) result).setValues((List<GregorianCalendar>) DefaultTypeConverter.INSTANCE
|
|
.convert(GregorianCalendar.class, (List<?>) value));
|
|
}
|
|
else
|
|
{
|
|
((PropertyDateTimeImpl) result).setValue(DefaultTypeConverter.INSTANCE.convert(GregorianCalendar.class,
|
|
value));
|
|
}
|
|
break;
|
|
case DECIMAL:
|
|
result = new PropertyDecimalImpl();
|
|
if (value instanceof List)
|
|
{
|
|
((PropertyDecimalImpl) result).setValues((List<BigDecimal>) DefaultTypeConverter.INSTANCE.convert(
|
|
BigDecimal.class, (List<?>) value));
|
|
}
|
|
else
|
|
{
|
|
((PropertyDecimalImpl) result).setValue(DefaultTypeConverter.INSTANCE.convert(BigDecimal.class, value));
|
|
}
|
|
break;
|
|
case HTML:
|
|
result = new PropertyHtmlImpl();
|
|
if (value instanceof List)
|
|
{
|
|
((PropertyHtmlImpl) result).setValues((List<String>) value);
|
|
}
|
|
else
|
|
{
|
|
((PropertyHtmlImpl) result).setValue((String) value);
|
|
}
|
|
break;
|
|
case ID:
|
|
result = new PropertyIdImpl();
|
|
if (value instanceof List)
|
|
{
|
|
((PropertyIdImpl) result).setValues((List<String>) value);
|
|
}
|
|
else
|
|
{
|
|
if (value instanceof NodeRef)
|
|
{
|
|
((PropertyIdImpl) result).setValue(value.toString());
|
|
}
|
|
else
|
|
{
|
|
((PropertyIdImpl) result).setValue((String) value);
|
|
}
|
|
}
|
|
break;
|
|
case INTEGER:
|
|
result = new PropertyIntegerImpl();
|
|
if (value instanceof List)
|
|
{
|
|
((PropertyIntegerImpl) result).setValues((List<BigInteger>) DefaultTypeConverter.INSTANCE.convert(
|
|
BigInteger.class, (List<?>) value));
|
|
}
|
|
else
|
|
{
|
|
((PropertyIntegerImpl) result).setValue(DefaultTypeConverter.INSTANCE.convert(BigInteger.class, value));
|
|
}
|
|
break;
|
|
case STRING:
|
|
result = new PropertyStringImpl();
|
|
if (value instanceof List)
|
|
{
|
|
((PropertyStringImpl) result).setValues((List<String>) value);
|
|
}
|
|
else
|
|
{
|
|
((PropertyStringImpl) result).setValue((String) value);
|
|
}
|
|
break;
|
|
case URI:
|
|
result = new PropertyUriImpl();
|
|
if (value instanceof List)
|
|
{
|
|
((PropertyUriImpl) result).setValues((List<String>) value);
|
|
}
|
|
else
|
|
{
|
|
((PropertyUriImpl) result).setValue((String) value);
|
|
}
|
|
break;
|
|
default:
|
|
throw new RuntimeException("Unknown datatype! Spec change?");
|
|
}
|
|
|
|
if (propDef != null)
|
|
{
|
|
result.setId(propDef.getPropertyDefinition().getId());
|
|
result.setQueryName(propDef.getPropertyDefinition().getQueryName());
|
|
result.setDisplayName(propDef.getPropertyDefinition().getDisplayName());
|
|
result.setLocalName(propDef.getPropertyDefinition().getLocalName());
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private Set<String> splitFilter(String filter)
|
|
{
|
|
if (filter == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
if (filter.trim().length() == 0)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
Set<String> result = new HashSet<String>();
|
|
for (String s : filter.split(","))
|
|
{
|
|
s = s.trim();
|
|
if (s.equals("*"))
|
|
{
|
|
return null;
|
|
}
|
|
else if (s.length() > 0)
|
|
{
|
|
result.add(s);
|
|
}
|
|
}
|
|
|
|
// set a few base properties
|
|
result.add(QUERY_NAME_OBJECT_ID);
|
|
result.add(QUERY_NAME_OBJECT_TYPE_ID);
|
|
result.add(QUERY_NAME_BASE_TYPE_ID);
|
|
|
|
return result;
|
|
}
|
|
|
|
public AllowableActions getAllowableActions(CMISNodeInfo info)
|
|
{
|
|
AllowableActionsImpl result = new AllowableActionsImpl();
|
|
Set<Action> allowableActions = new HashSet<Action>();
|
|
result.setAllowableActions(allowableActions);
|
|
|
|
for (CMISActionEvaluator evaluator : info.getType().getActionEvaluators().values())
|
|
{
|
|
if (evaluator.isAllowed(info))
|
|
{
|
|
allowableActions.add(evaluator.getAction());
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public List<ObjectData> getRelationships(NodeRef nodeRef, IncludeRelationships includeRelationships)
|
|
{
|
|
List<ObjectData> result = new ArrayList<ObjectData>();
|
|
|
|
if (nodeRef.getStoreRef().getProtocol().equals(VersionBaseModel.STORE_PROTOCOL))
|
|
{
|
|
// relationships from and to versions are not preserved
|
|
return result;
|
|
}
|
|
|
|
// get relationships
|
|
List<AssociationRef> assocs = new ArrayList<AssociationRef>();
|
|
if (includeRelationships == IncludeRelationships.SOURCE || includeRelationships == IncludeRelationships.BOTH)
|
|
{
|
|
assocs.addAll(nodeService.getTargetAssocs(nodeRef, RegexQNamePattern.MATCH_ALL));
|
|
}
|
|
if (includeRelationships == IncludeRelationships.TARGET || includeRelationships == IncludeRelationships.BOTH)
|
|
{
|
|
assocs.addAll(nodeService.getSourceAssocs(nodeRef, RegexQNamePattern.MATCH_ALL));
|
|
}
|
|
|
|
// filter relationships that not map the CMIS domain model
|
|
for (AssociationRef assocRef : assocs)
|
|
{
|
|
TypeDefinitionWrapper assocTypeDef = cmisDictionaryService.findAssocType(assocRef.getTypeQName());
|
|
if (assocTypeDef == null || getType(assocRef.getSourceRef()) == null
|
|
|| getType(assocRef.getTargetRef()) == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
try
|
|
{
|
|
result.add(createCMISObject(createNodeInfo(assocRef), null, false, IncludeRelationships.NONE,
|
|
RENDITION_NONE, false, false));
|
|
}
|
|
catch(CmisObjectNotFoundException e)
|
|
{
|
|
// ignore objects that have not been found (perhaps because their type is unknown to CMIS)
|
|
}
|
|
catch (AccessDeniedException e)
|
|
{
|
|
// skip
|
|
}
|
|
// TODO: Somewhere this has not been wrapped correctly
|
|
catch (net.sf.acegisecurity.AccessDeniedException e)
|
|
{
|
|
// skip
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public ObjectList getObjectRelationships(NodeRef nodeRef, RelationshipDirection relationshipDirection,
|
|
String typeId, String filter, Boolean includeAllowableActions, BigInteger maxItems, BigInteger skipCount)
|
|
{
|
|
ObjectListImpl result = new ObjectListImpl();
|
|
result.setHasMoreItems(false);
|
|
result.setNumItems(BigInteger.ZERO);
|
|
result.setObjects(new ArrayList<ObjectData>());
|
|
|
|
if (nodeRef.getStoreRef().getProtocol().equals(VersionBaseModel.STORE_PROTOCOL))
|
|
{
|
|
// relationships from and to versions are not preserved
|
|
return result;
|
|
}
|
|
|
|
// get relationships
|
|
List<AssociationRef> assocs = new ArrayList<AssociationRef>();
|
|
if (relationshipDirection == RelationshipDirection.SOURCE
|
|
|| relationshipDirection == RelationshipDirection.EITHER)
|
|
{
|
|
assocs.addAll(nodeService.getTargetAssocs(nodeRef, RegexQNamePattern.MATCH_ALL));
|
|
}
|
|
if (relationshipDirection == RelationshipDirection.TARGET
|
|
|| relationshipDirection == RelationshipDirection.EITHER)
|
|
{
|
|
assocs.addAll(nodeService.getSourceAssocs(nodeRef, RegexQNamePattern.MATCH_ALL));
|
|
}
|
|
|
|
int skip = (skipCount == null ? 0 : skipCount.intValue());
|
|
int max = (maxItems == null ? Integer.MAX_VALUE : maxItems.intValue());
|
|
int counter = 0;
|
|
boolean hasMore = false;
|
|
|
|
if (max > 0)
|
|
{
|
|
// filter relationships that not map the CMIS domain model
|
|
for (AssociationRef assocRef : assocs)
|
|
{
|
|
TypeDefinitionWrapper assocTypeDef = cmisDictionaryService.findAssocType(assocRef.getTypeQName());
|
|
if (assocTypeDef == null || getType(assocRef.getSourceRef()) == null
|
|
|| getType(assocRef.getTargetRef()) == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ((typeId != null) && !assocTypeDef.getTypeId().equals(typeId))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
counter++;
|
|
|
|
if (skip > 0)
|
|
{
|
|
skip--;
|
|
continue;
|
|
}
|
|
|
|
max--;
|
|
if (max > 0)
|
|
{
|
|
try
|
|
{
|
|
result.getObjects().add(
|
|
createCMISObject(createNodeInfo(assocRef), filter, includeAllowableActions,
|
|
IncludeRelationships.NONE, RENDITION_NONE, false, false));
|
|
}
|
|
catch(CmisObjectNotFoundException e)
|
|
{
|
|
// ignore objects that have not been found (perhaps because their type is unknown to CMIS)
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hasMore = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
result.setNumItems(BigInteger.valueOf(counter));
|
|
result.setHasMoreItems(hasMore);
|
|
|
|
return result;
|
|
}
|
|
|
|
public List<RenditionData> getRenditions(NodeRef nodeRef, String renditionFilter, BigInteger maxItems,
|
|
BigInteger skipCount)
|
|
{
|
|
CMISRenditionMapping mapping = getRenditionMapping();
|
|
return mapping.getRenditions(nodeRef, renditionFilter, maxItems, skipCount);
|
|
}
|
|
|
|
public Acl getACL(NodeRef nodeRef, boolean onlyBasicPermissions)
|
|
{
|
|
AccessControlListImpl result = new AccessControlListImpl();
|
|
|
|
// get the permissions and sort them
|
|
ArrayList<AccessPermission> ordered = new ArrayList<AccessPermission>(
|
|
permissionService.getAllSetPermissions(nodeRef));
|
|
Collections.sort(ordered, new AccessPermissionComparator());
|
|
|
|
// remove denied permissions and create OpenCMIS objects
|
|
Map<String, Map<Boolean, AccessControlEntryImpl>> aceMap = new HashMap<String, Map<Boolean, AccessControlEntryImpl>>();
|
|
for (AccessPermission entry : ordered)
|
|
{
|
|
if (entry.getAccessStatus() == AccessStatus.ALLOWED)
|
|
{
|
|
// add allowed entries
|
|
Map<Boolean, AccessControlEntryImpl> directAce = aceMap.get(entry.getAuthority());
|
|
if (directAce == null)
|
|
{
|
|
directAce = new HashMap<Boolean, AccessControlEntryImpl>();
|
|
aceMap.put(entry.getAuthority(), directAce);
|
|
}
|
|
|
|
AccessControlEntryImpl ace = directAce.get(entry.isSetDirectly());
|
|
if (ace == null)
|
|
{
|
|
ace = new AccessControlEntryImpl();
|
|
ace.setPrincipal(new AccessControlPrincipalDataImpl(entry.getAuthority()));
|
|
ace.setPermissions(new ArrayList<String>());
|
|
ace.setDirect(entry.isSetDirectly());
|
|
directAce.put(entry.isSetDirectly(), ace);
|
|
}
|
|
|
|
ace.getPermissions().add(entry.getPermission());
|
|
}
|
|
else if (entry.getAccessStatus() == AccessStatus.DENIED)
|
|
{
|
|
// remove denied entries
|
|
Map<Boolean, AccessControlEntryImpl> directAce = aceMap.get(entry.getAuthority());
|
|
if (directAce != null)
|
|
{
|
|
for (AccessControlEntryImpl ace : directAce.values())
|
|
{
|
|
ace.getPermissions().remove(entry.getPermission());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// adjust permissions, add CMIS permissions and add ACEs to ACL
|
|
List<Ace> aces = new ArrayList<Ace>();
|
|
result.setAces(aces);
|
|
for (Map<Boolean, AccessControlEntryImpl> bothAces : aceMap.values())
|
|
{
|
|
// get, translate and set direct ACE
|
|
AccessControlEntryImpl directAce = bothAces.get(true);
|
|
if ((directAce != null) && (!directAce.getPermissions().isEmpty()))
|
|
{
|
|
directAce.setPermissions(translatePermmissionsToCMIS(directAce.getPermissions(), onlyBasicPermissions));
|
|
aces.add(directAce);
|
|
}
|
|
|
|
// get, translate, remove duplicate permissions and set indirect ACE
|
|
AccessControlEntryImpl indirectAce = bothAces.get(false);
|
|
if ((indirectAce != null) && (!indirectAce.getPermissions().isEmpty()))
|
|
{
|
|
indirectAce.setPermissions(translatePermmissionsToCMIS(indirectAce.getPermissions(),
|
|
onlyBasicPermissions));
|
|
|
|
// remove permissions that are already set in the direct ACE
|
|
if ((directAce != null) && (!directAce.getPermissions().isEmpty()))
|
|
{
|
|
indirectAce.getPermissions().removeAll(directAce.getPermissions());
|
|
}
|
|
|
|
aces.add(indirectAce);
|
|
}
|
|
}
|
|
|
|
result.setExact(!onlyBasicPermissions);
|
|
|
|
return result;
|
|
}
|
|
|
|
private List<String> translatePermmissionsToCMIS(List<String> permissions, boolean onlyBasicPermissions)
|
|
{
|
|
Set<String> result = new TreeSet<String>();
|
|
|
|
for (String permission : permissions)
|
|
{
|
|
PermissionReference permissionReference = permissionModelDao.getPermissionReference(null, permission);
|
|
|
|
// check for full permissions
|
|
if (permissionModelDao.hasFull(permissionReference))
|
|
{
|
|
result.add(BasicPermissions.READ);
|
|
result.add(BasicPermissions.WRITE);
|
|
result.add(BasicPermissions.ALL);
|
|
}
|
|
|
|
// check short forms
|
|
Set<PermissionReference> longForms = permissionModelDao.getGranteePermissions(permissionReference);
|
|
|
|
HashSet<String> shortForms = new HashSet<String>();
|
|
for (PermissionReference longForm : longForms)
|
|
{
|
|
shortForms.add(permissionModelDao.isUnique(longForm) ? longForm.getName() : longForm.toString());
|
|
}
|
|
|
|
for (String perm : shortForms)
|
|
{
|
|
if (PermissionService.READ.equals(perm))
|
|
{
|
|
result.add(BasicPermissions.READ);
|
|
}
|
|
else if (PermissionService.WRITE.equals(perm))
|
|
{
|
|
result.add(BasicPermissions.WRITE);
|
|
}
|
|
else if (PermissionService.ALL_PERMISSIONS.equals(perm))
|
|
{
|
|
result.add(BasicPermissions.READ);
|
|
result.add(BasicPermissions.WRITE);
|
|
result.add(BasicPermissions.ALL);
|
|
}
|
|
}
|
|
|
|
// check the permission
|
|
if (PermissionService.READ.equals(permission))
|
|
{
|
|
result.add(BasicPermissions.READ);
|
|
}
|
|
else if (PermissionService.WRITE.equals(permission))
|
|
{
|
|
result.add(BasicPermissions.WRITE);
|
|
}
|
|
else if (PermissionService.ALL_PERMISSIONS.equals(permission))
|
|
{
|
|
result.add(BasicPermissions.READ);
|
|
result.add(BasicPermissions.WRITE);
|
|
result.add(BasicPermissions.ALL);
|
|
}
|
|
|
|
// expand native permissions
|
|
if (!onlyBasicPermissions)
|
|
{
|
|
if (permission.startsWith("{"))
|
|
{
|
|
result.add(permission);
|
|
}
|
|
else
|
|
{
|
|
result.add(permissionReference.toString());
|
|
}
|
|
}
|
|
}
|
|
|
|
return new ArrayList<String>(result);
|
|
}
|
|
|
|
public static class AccessPermissionComparator implements Comparator<AccessPermission>
|
|
{
|
|
public int compare(AccessPermission left, AccessPermission right)
|
|
{
|
|
if (left.getPosition() != right.getPosition())
|
|
{
|
|
return right.getPosition() - left.getPosition();
|
|
}
|
|
else
|
|
{
|
|
if (left.getAccessStatus() != right.getAccessStatus())
|
|
{
|
|
return (left.getAccessStatus() == AccessStatus.DENIED) ? -1 : 1;
|
|
}
|
|
else
|
|
{
|
|
int compare = left.getAuthority().compareTo(right.getAuthority());
|
|
if (compare != 0)
|
|
{
|
|
return compare;
|
|
}
|
|
else
|
|
{
|
|
return (left.getPermission().compareTo(right.getPermission()));
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Applies the given ACLs.
|
|
*/
|
|
public void applyACL(NodeRef nodeRef, TypeDefinitionWrapper type, Acl addAces, Acl removeAces)
|
|
{
|
|
boolean hasAdd = (addAces != null) && (addAces.getAces() != null) && !addAces.getAces().isEmpty();
|
|
boolean hasRemove = (removeAces != null) && (removeAces.getAces() != null) && !removeAces.getAces().isEmpty();
|
|
|
|
if (!hasAdd && !hasRemove)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!type.getTypeDefinition(false).isControllableAcl())
|
|
{
|
|
throw new CmisConstraintException("Object is not ACL controllable!");
|
|
}
|
|
|
|
// remove permissions
|
|
if (hasRemove)
|
|
{
|
|
Set<AccessPermission> permissions = permissionService.getAllSetPermissions(nodeRef);
|
|
for (Ace ace : removeAces.getAces())
|
|
{
|
|
String principalId = ace.getPrincipalId();
|
|
if (CMIS_USER.equals(principalId))
|
|
{
|
|
principalId = AuthenticationUtil.getFullyAuthenticatedUser();
|
|
}
|
|
|
|
for (String permission : translatePermissionsFromCMIS(ace.getPermissions()))
|
|
{
|
|
|
|
AccessPermission toCheck = new AccessPermissionImpl(permission, AccessStatus.ALLOWED, principalId,
|
|
0);
|
|
if (!permissions.contains(toCheck))
|
|
{
|
|
throw new CmisConstraintException("No matching ACE found to remove!");
|
|
}
|
|
|
|
permissionService.deletePermission(nodeRef, principalId, permission);
|
|
}
|
|
}
|
|
}
|
|
|
|
// add permissions
|
|
if (hasAdd)
|
|
{
|
|
for (Ace ace : addAces.getAces())
|
|
{
|
|
String principalId = ace.getPrincipalId();
|
|
if (CMIS_USER.equals(principalId))
|
|
{
|
|
principalId = AuthenticationUtil.getFullyAuthenticatedUser();
|
|
}
|
|
|
|
for (String permission : translatePermissionsFromCMIS(ace.getPermissions()))
|
|
{
|
|
permissionService.setPermission(nodeRef, principalId, permission, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the given ACL.
|
|
*/
|
|
public void applyACL(NodeRef nodeRef, TypeDefinitionWrapper type, Acl aces)
|
|
{
|
|
boolean hasAces = (aces != null) && (aces.getAces() != null) && !aces.getAces().isEmpty();
|
|
|
|
if (!hasAces)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!type.getTypeDefinition(false).isControllableAcl())
|
|
{
|
|
throw new CmisConstraintException("Object is not ACL controllable!");
|
|
}
|
|
|
|
Set<AccessPermission> currentAces = permissionService.getAllSetPermissions(nodeRef);
|
|
|
|
// remove all permissions
|
|
permissionService.deletePermissions(nodeRef);
|
|
|
|
// set new permissions
|
|
for (Ace ace : aces.getAces())
|
|
{
|
|
String principalId = ace.getPrincipalId();
|
|
if (CMIS_USER.equals(principalId))
|
|
{
|
|
principalId = AuthenticationUtil.getFullyAuthenticatedUser();
|
|
}
|
|
|
|
List<String> permissions = translatePermissionsFromCMIS(ace.getPermissions());
|
|
normalisePermissions(currentAces, permissions);
|
|
for (String permission : permissions)
|
|
{
|
|
permissionService.setPermission(nodeRef, principalId, permission, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* ALF-11868: the cmis client library may incorrectly send READ or WRITE permissions to applyAcl.
|
|
* This method works around this by "normalising" permissions:
|
|
*
|
|
* <ul>
|
|
* <li> the WRITE permission is removed from permissions if the cmis:write permission is being removed i.e. is in currentAccessPermissions but not in newPermissions
|
|
* <li> the cmis:write permission is removed from permissions if the WRITE permission is being removed i.e. is in currentAccessPermissions but not in newPermissions
|
|
* <li> the READ permission is removed from permissions if the cmis:read permission is being removed i.e. is in currentAccessPermissions but not in newPermissions
|
|
* <li> the cmis:read permission is removed from permissions if the READ permission is being removed i.e. is in currentAccessPermissions but not in newPermissions
|
|
* </ul>
|
|
*/
|
|
private void normalisePermissions(Set<AccessPermission> currentAccessPermissions, List<String> newPermissions)
|
|
{
|
|
Set<String> currentPermissions = new HashSet<String>(currentAccessPermissions.size());
|
|
for(AccessPermission accessPermission : currentAccessPermissions)
|
|
{
|
|
currentPermissions.add(accessPermission.getPermission());
|
|
}
|
|
|
|
if(currentPermissions.contains(PermissionService.WRITE) && !newPermissions.contains(BasicPermissions.WRITE) && newPermissions.contains(PermissionService.WRITE))
|
|
{
|
|
// cmis:write is being removed, so remove WRITE from permissions
|
|
newPermissions.remove(PermissionService.WRITE);
|
|
}
|
|
|
|
if(currentPermissions.contains(PermissionService.WRITE) && !newPermissions.contains(PermissionService.WRITE) && newPermissions.contains(BasicPermissions.WRITE))
|
|
{
|
|
// WRITE is being removed, so remove cmis:write from permissions
|
|
newPermissions.remove(BasicPermissions.WRITE);
|
|
}
|
|
|
|
if(currentPermissions.contains(PermissionService.READ) && !newPermissions.contains(BasicPermissions.READ) && newPermissions.contains(PermissionService.READ))
|
|
{
|
|
// cmis:read is being removed, so remove READ from permissions
|
|
newPermissions.remove(PermissionService.READ);
|
|
}
|
|
|
|
if(currentPermissions.contains(PermissionService.READ) && !newPermissions.contains(PermissionService.READ) && newPermissions.contains(BasicPermissions.READ))
|
|
{
|
|
// READ is being removed, so remove cmis:read from permissions
|
|
newPermissions.remove(BasicPermissions.READ);
|
|
}
|
|
}
|
|
|
|
private List<String> translatePermissionsFromCMIS(List<String> permissions)
|
|
{
|
|
List<String> result = new ArrayList<String>();
|
|
|
|
if (permissions == null)
|
|
{
|
|
return result;
|
|
}
|
|
|
|
for (String permission : permissions)
|
|
{
|
|
if (permission == null)
|
|
{
|
|
throw new CmisConstraintException("Invalid null permission!");
|
|
}
|
|
|
|
if (BasicPermissions.READ.equals(permission))
|
|
{
|
|
result.add(PermissionService.READ);
|
|
}
|
|
else if (BasicPermissions.WRITE.equals(permission))
|
|
{
|
|
result.add(PermissionService.WRITE);
|
|
}
|
|
else if (BasicPermissions.ALL.equals(permission))
|
|
{
|
|
result.add(PermissionService.ALL_PERMISSIONS);
|
|
}
|
|
else if (!permission.startsWith("{"))
|
|
{
|
|
result.add(permission);
|
|
}
|
|
else
|
|
{
|
|
int sepIndex = permission.lastIndexOf('.');
|
|
if (sepIndex == -1)
|
|
{
|
|
result.add(permission);
|
|
}
|
|
else
|
|
{
|
|
result.add(permission.substring(sepIndex + 1));
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public void applyPolicies(NodeRef nodeRef, TypeDefinitionWrapper type, List<String> policies)
|
|
{
|
|
if ((policies == null) || (policies.isEmpty()))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!type.getTypeDefinition(false).isControllablePolicy())
|
|
{
|
|
throw new CmisConstraintException("Object is not policy controllable!");
|
|
}
|
|
|
|
// nothing else to do...
|
|
}
|
|
|
|
public ObjectList query(String statement, Boolean includeAllowableActions,
|
|
IncludeRelationships includeRelationships, String renditionFilter, BigInteger maxItems, BigInteger skipCount)
|
|
{
|
|
// prepare results
|
|
ObjectListImpl result = new ObjectListImpl();
|
|
result.setObjects(new ArrayList<ObjectData>());
|
|
|
|
// prepare query
|
|
CMISQueryOptions options = new CMISQueryOptions(statement, getRootStoreRef());
|
|
options.setQueryMode(CMISQueryMode.CMS_WITH_ALFRESCO_EXTENSIONS);
|
|
|
|
int skip = 0;
|
|
if ((skipCount != null) && (skipCount.intValue() >= 0))
|
|
{
|
|
skip = skipCount.intValue();
|
|
options.setSkipCount(skip);
|
|
}
|
|
|
|
if ((maxItems != null) && (maxItems.intValue() >= 0))
|
|
{
|
|
options.setMaxItems(maxItems.intValue());
|
|
}
|
|
|
|
boolean fetchObject = includeAllowableActions || (includeRelationships != IncludeRelationships.NONE)
|
|
|| (!RENDITION_NONE.equals(renditionFilter));
|
|
|
|
// query
|
|
CMISResultSet rs = cmisQueryService.query(options);
|
|
try
|
|
{
|
|
CMISResultSetColumn[] columns = rs.getMetaData().getColumns();
|
|
|
|
for (CMISResultSetRow row : rs)
|
|
{
|
|
ObjectDataImpl hit = new ObjectDataImpl();
|
|
PropertiesImpl properties = new PropertiesImpl();
|
|
hit.setProperties(properties);
|
|
|
|
Map<String, Serializable> values = row.getValues();
|
|
|
|
for (CMISResultSetColumn column : columns)
|
|
{
|
|
AbstractPropertyData<?> property = getProperty(column.getCMISDataType(),
|
|
column.getCMISPropertyDefinition(), values.get(column.getName()));
|
|
property.setQueryName(column.getName());
|
|
properties.addProperty(property);
|
|
}
|
|
|
|
if (fetchObject)
|
|
{
|
|
NodeRef nodeRef = row.getNodeRef();
|
|
TypeDefinitionWrapper type = getType(nodeRef);
|
|
if (type == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// set allowable actions
|
|
if (includeAllowableActions)
|
|
{
|
|
hit.setAllowableActions(getAllowableActions(createNodeInfo(nodeRef)));
|
|
}
|
|
|
|
// set relationships
|
|
if (includeRelationships != IncludeRelationships.NONE)
|
|
{
|
|
hit.setRelationships(getRelationships(nodeRef, includeRelationships));
|
|
}
|
|
|
|
// set renditions
|
|
if (!RENDITION_NONE.equals(renditionFilter))
|
|
{
|
|
List<RenditionData> renditions = getRenditions(nodeRef, renditionFilter, null, null);
|
|
if ((renditions != null) && (!renditions.isEmpty()))
|
|
{
|
|
hit.setRenditions(renditions);
|
|
}
|
|
}
|
|
}
|
|
|
|
result.getObjects().add(hit);
|
|
}
|
|
|
|
int length = rs.getLength();
|
|
if(length != -1)
|
|
{
|
|
result.setNumItems(BigInteger.valueOf(length));
|
|
}
|
|
result.setHasMoreItems(rs.hasMore());
|
|
|
|
} finally
|
|
{
|
|
rs.close();
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Sets property values.
|
|
*/
|
|
public void setProperties(NodeRef nodeRef, TypeDefinitionWrapper type, Properties properties, String... exclude)
|
|
{
|
|
if (properties == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (PropertyData<?> property : properties.getPropertyList())
|
|
{
|
|
if (Arrays.binarySearch(exclude, property.getId()) < 0)
|
|
{
|
|
setProperty(nodeRef, type, property);
|
|
}
|
|
}
|
|
|
|
List<CmisExtensionElement> extensions = properties.getExtensions();
|
|
if (extensions != null)
|
|
{
|
|
for (CmisExtensionElement extension : extensions)
|
|
{
|
|
if (ALFRESCO_EXTENSION_NAMESPACE.equals(extension.getNamespace())
|
|
&& SET_ASPECTS.equals(extension.getName()))
|
|
{
|
|
setAspectProperties(nodeRef, extension);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void setAspectProperties(NodeRef nodeRef, CmisExtensionElement aspectExtension)
|
|
{
|
|
if (aspectExtension.getChildren() == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
List<String> aspectsToAdd = new ArrayList<String>();
|
|
List<String> aspectsToRemove = new ArrayList<String>();
|
|
Map<QName, List<Serializable>> aspectProperties = new HashMap<QName, List<Serializable>>();
|
|
|
|
for (CmisExtensionElement extension : aspectExtension.getChildren())
|
|
{
|
|
if (!ALFRESCO_EXTENSION_NAMESPACE.equals(extension.getNamespace()))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (ASPECTS_TO_ADD.equals(extension.getName()) && (extension.getValue() != null))
|
|
{
|
|
aspectsToAdd.add(extension.getValue());
|
|
}
|
|
else if (ASPECTS_TO_REMOVE.equals(extension.getName()) && (extension.getValue() != null))
|
|
{
|
|
aspectsToRemove.add(extension.getValue());
|
|
}
|
|
else if (PROPERTIES.equals(extension.getName()) && (extension.getChildren() != null))
|
|
{
|
|
for (CmisExtensionElement property : extension.getChildren())
|
|
{
|
|
if (!property.getName().startsWith("property"))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
String propertyId = (property.getAttributes() == null ? null : property.getAttributes().get(
|
|
"propertyDefinitionId"));
|
|
if ((propertyId == null) || (property.getChildren() == null))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
PropertyType propertyType = PropertyType.STRING;
|
|
DatatypeFactory df = null;
|
|
if (property.getName().equals("propertyBoolean"))
|
|
{
|
|
propertyType = PropertyType.BOOLEAN;
|
|
}
|
|
else if (property.getName().equals("propertyInteger"))
|
|
{
|
|
propertyType = PropertyType.INTEGER;
|
|
}
|
|
else if (property.getName().equals("propertyDateTime"))
|
|
{
|
|
propertyType = PropertyType.DATETIME;
|
|
try
|
|
{
|
|
df = DatatypeFactory.newInstance();
|
|
}
|
|
catch (DatatypeConfigurationException e)
|
|
{
|
|
throw new CmisRuntimeException("Aspect conversation exception: " + e.getMessage(), e);
|
|
}
|
|
}
|
|
else if (property.getName().equals("propertyDecimal"))
|
|
{
|
|
propertyType = PropertyType.DECIMAL;
|
|
}
|
|
|
|
ArrayList<Serializable> values = new ArrayList<Serializable>();
|
|
if (property.getChildren() != null)
|
|
{
|
|
try
|
|
{
|
|
for (CmisExtensionElement valueElement : property.getChildren())
|
|
{
|
|
if ("value".equals(valueElement.getName()))
|
|
{
|
|
switch (propertyType)
|
|
{
|
|
case BOOLEAN:
|
|
values.add(Boolean.parseBoolean(valueElement.getValue()));
|
|
break;
|
|
case DATETIME:
|
|
values.add(df.newXMLGregorianCalendar(valueElement.getValue())
|
|
.toGregorianCalendar());
|
|
break;
|
|
case INTEGER:
|
|
values.add(new BigInteger(valueElement.getValue()));
|
|
break;
|
|
case DECIMAL:
|
|
values.add(new BigDecimal(valueElement.getValue()));
|
|
break;
|
|
default:
|
|
values.add(valueElement.getValue());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
throw new CmisInvalidArgumentException("Invalid property aspect value: " + propertyId, e);
|
|
}
|
|
}
|
|
|
|
aspectProperties.put(QName.createQName(propertyId, namespaceService), values);
|
|
}
|
|
}
|
|
}
|
|
|
|
// remove and add aspects
|
|
String aspectType = null;
|
|
try
|
|
{
|
|
for (String aspect : aspectsToRemove)
|
|
{
|
|
aspectType = aspect;
|
|
TypeDefinitionWrapper type = getType(aspect);
|
|
if (type == null)
|
|
{
|
|
throw new CmisInvalidArgumentException("Invalid aspect: " + aspectType);
|
|
}
|
|
nodeService.removeAspect(nodeRef, type.getAlfrescoName());
|
|
}
|
|
|
|
for (String aspect : aspectsToAdd)
|
|
{
|
|
aspectType = aspect;
|
|
TypeDefinitionWrapper type = getType(aspect);
|
|
if (type == null)
|
|
{
|
|
throw new CmisInvalidArgumentException("Invalid aspect: " + aspectType);
|
|
}
|
|
nodeService.addAspect(nodeRef, type.getAlfrescoName(),
|
|
Collections.<QName, Serializable> emptyMap());
|
|
}
|
|
}
|
|
catch (InvalidAspectException e)
|
|
{
|
|
throw new CmisInvalidArgumentException("Invalid aspect: " + aspectType);
|
|
}
|
|
catch (InvalidNodeRefException e)
|
|
{
|
|
throw new CmisInvalidArgumentException("Invalid node: " + nodeRef);
|
|
}
|
|
|
|
// set property
|
|
for (Map.Entry<QName, List<Serializable>> property : aspectProperties.entrySet())
|
|
{
|
|
if (property.getValue().isEmpty())
|
|
{
|
|
nodeService.removeProperty(nodeRef, property.getKey());
|
|
}
|
|
else
|
|
{
|
|
nodeService.setProperty(nodeRef, property.getKey(), property.getValue().size() == 1 ? property
|
|
.getValue().get(0) : (Serializable) property.getValue());
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets a property value.
|
|
*/
|
|
public void setProperty(NodeRef nodeRef, TypeDefinitionWrapper type, PropertyData<?> property)
|
|
{
|
|
if (property == null)
|
|
{
|
|
throw new CmisInvalidArgumentException("Cannot process not null property!");
|
|
}
|
|
|
|
PropertyDefinitionWrapper propDef = type.getPropertyById(property.getId());
|
|
if (propDef == null)
|
|
{
|
|
throw new CmisInvalidArgumentException("Property " + property.getId() + " is unknown!");
|
|
}
|
|
|
|
Updatability updatability = propDef.getPropertyDefinition().getUpdatability();
|
|
if ((updatability == Updatability.READONLY)
|
|
|| (updatability == Updatability.WHENCHECKEDOUT && !checkOutCheckInService.isWorkingCopy(nodeRef)))
|
|
{
|
|
throw new CmisInvalidArgumentException("Property " + property.getId() + " is read-only!");
|
|
}
|
|
|
|
QName propertyQName = propDef.getPropertyAccessor().getMappedProperty();
|
|
if (propertyQName == null)
|
|
{
|
|
throw new CmisConstraintException("Unable to set property " + property.getId() + "!");
|
|
}
|
|
|
|
// get the value
|
|
Serializable value = getValue(property, propDef.getPropertyDefinition().getCardinality() == Cardinality.MULTI);
|
|
|
|
if (property.getId().equals(PropertyIds.NAME))
|
|
{
|
|
if (!(value instanceof String))
|
|
{
|
|
throw new CmisInvalidArgumentException("Object name must be a string!");
|
|
}
|
|
|
|
try
|
|
{
|
|
fileFolderService.rename(nodeRef, value.toString());
|
|
}
|
|
catch (FileExistsException e)
|
|
{
|
|
throw new CmisContentAlreadyExistsException("An object with this name already exists!", e);
|
|
}
|
|
catch (FileNotFoundException e)
|
|
{
|
|
throw new CmisInvalidArgumentException("Object with id " + nodeRef.toString() + " not found!");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (value == null)
|
|
{
|
|
nodeService.removeProperty(nodeRef, propertyQName);
|
|
}
|
|
else
|
|
{
|
|
nodeService.setProperty(nodeRef, propertyQName, value);
|
|
}
|
|
}
|
|
}
|
|
|
|
private Serializable getValue(PropertyData<?> property, boolean isMultiValue)
|
|
{
|
|
if ((property.getValues() == null) || (property.getValues().isEmpty()))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
if (isMultiValue)
|
|
{
|
|
return (Serializable) property.getValues();
|
|
}
|
|
|
|
return (Serializable) property.getValues().get(0);
|
|
}
|
|
|
|
/**
|
|
* Returns content changes.
|
|
*/
|
|
public ObjectList getContentChanges(Holder<String> changeLogToken, BigInteger maxItems)
|
|
{
|
|
final ObjectListImpl result = new ObjectListImpl();
|
|
result.setObjects(new ArrayList<ObjectData>());
|
|
|
|
EntryIdCallback changeLogCollectingCallback = new EntryIdCallback(true)
|
|
{
|
|
@Override
|
|
public boolean handleAuditEntry(Long entryId, String user, long time, Map<String, Serializable> values)
|
|
{
|
|
result.getObjects().addAll(createChangeEvents(time, values));
|
|
return super.handleAuditEntry(entryId, user, time, values);
|
|
}
|
|
};
|
|
|
|
Long from = null;
|
|
if ((changeLogToken != null) && (changeLogToken.getValue() != null))
|
|
{
|
|
try
|
|
{
|
|
from = Long.parseLong(changeLogToken.getValue());
|
|
}
|
|
catch (NumberFormatException e)
|
|
{
|
|
throw new CmisInvalidArgumentException("Invalid change log token: " + changeLogToken);
|
|
}
|
|
}
|
|
|
|
AuditQueryParameters params = new AuditQueryParameters();
|
|
params.setApplicationName(CMIS_CHANGELOG_AUDIT_APPLICATION);
|
|
params.setForward(true);
|
|
params.setFromId(from);
|
|
|
|
int maxResults = (maxItems == null ? 0 : maxItems.intValue());
|
|
maxResults = (maxResults < 1 ? 0 : maxResults + 1);
|
|
|
|
auditService.auditQuery(changeLogCollectingCallback, params, maxResults);
|
|
|
|
String newChangeLogToken = null;
|
|
if (maxResults > 0)
|
|
{
|
|
if (result.getObjects().size() >= maxResults)
|
|
{
|
|
newChangeLogToken = result.getObjects().remove(result.getObjects().size() - 1).getId();
|
|
result.setHasMoreItems(true);
|
|
}
|
|
else
|
|
{
|
|
result.setHasMoreItems(false);
|
|
}
|
|
}
|
|
|
|
if (changeLogToken != null)
|
|
{
|
|
changeLogToken.setValue(newChangeLogToken);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
private List<ObjectData> createChangeEvents(long time, Map<String, Serializable> values)
|
|
{
|
|
List<ObjectData> result = new ArrayList<ObjectData>();
|
|
|
|
if ((values == null) || (values.size() == 0))
|
|
{
|
|
return result;
|
|
}
|
|
|
|
GregorianCalendar changeTime = new GregorianCalendar();
|
|
changeTime.setTimeInMillis(time);
|
|
|
|
String appPath = "/" + CMIS_CHANGELOG_AUDIT_APPLICATION + "/";
|
|
|
|
for (Entry<String, Serializable> entry : values.entrySet())
|
|
{
|
|
if ((entry.getKey() == null) || (!(entry.getValue() instanceof Map)))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
String path = entry.getKey();
|
|
if (!path.startsWith(appPath))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
ChangeType changeType = null;
|
|
String changePath = path.substring(appPath.length()).toLowerCase();
|
|
for (ChangeType c : ChangeType.values())
|
|
{
|
|
if (changePath.startsWith(c.value().toLowerCase()))
|
|
{
|
|
changeType = c;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (changeType == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Map<String, Serializable> valueMap = (Map<String, Serializable>) entry.getValue();
|
|
String objectId = (String) valueMap.get(CMISChangeLogDataExtractor.KEY_OBJECT_ID);
|
|
|
|
// build object
|
|
ObjectDataImpl object = new ObjectDataImpl();
|
|
result.add(object);
|
|
|
|
PropertiesImpl properties = new PropertiesImpl();
|
|
object.setProperties(properties);
|
|
PropertyIdImpl objectIdProperty = new PropertyIdImpl(PropertyIds.OBJECT_ID, objectId);
|
|
properties.addProperty(objectIdProperty);
|
|
|
|
ChangeEventInfoDataImpl changeEvent = new ChangeEventInfoDataImpl();
|
|
object.setChangeEventInfo(changeEvent);
|
|
changeEvent.setChangeType(changeType);
|
|
changeEvent.setChangeTime(changeTime);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private class EntryIdCallback implements AuditQueryCallback
|
|
{
|
|
private final boolean valuesRequired;
|
|
private Long entryId;
|
|
|
|
public EntryIdCallback(boolean valuesRequired)
|
|
{
|
|
this.valuesRequired = valuesRequired;
|
|
}
|
|
|
|
public String getEntryId()
|
|
{
|
|
return entryId == null ? null : entryId.toString();
|
|
}
|
|
|
|
public boolean valuesRequired()
|
|
{
|
|
return this.valuesRequired;
|
|
}
|
|
|
|
public final boolean handleAuditEntry(Long entryId, String applicationName, String user, long time,
|
|
Map<String, Serializable> values)
|
|
{
|
|
if (applicationName.equals(CMIS_CHANGELOG_AUDIT_APPLICATION))
|
|
{
|
|
return handleAuditEntry(entryId, user, time, values);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public boolean handleAuditEntry(Long entryId, String user, long time, Map<String, Serializable> values)
|
|
{
|
|
this.entryId = entryId;
|
|
return true;
|
|
}
|
|
|
|
public boolean handleAuditEntryError(Long entryId, String errorMsg, Throwable error)
|
|
{
|
|
throw new CmisRuntimeException("Audit entry " + entryId + ": " + errorMsg, error);
|
|
}
|
|
};
|
|
|
|
// --------------------------------------------------------------
|
|
// OpenCMIS methods
|
|
// --------------------------------------------------------------
|
|
|
|
/**
|
|
* Returns the value of the given property if it exists and is of the
|
|
* correct type.
|
|
*/
|
|
public String getStringProperty(Properties properties, String propertyId)
|
|
{
|
|
if ((properties == null) || (properties.getProperties() == null))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
PropertyData<?> property = properties.getProperties().get(propertyId);
|
|
if (!(property instanceof PropertyString))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return ((PropertyString) property).getFirstValue();
|
|
}
|
|
|
|
/**
|
|
* Returns the value of the given property if it exists and is of the
|
|
* correct type.
|
|
*/
|
|
public String getIdProperty(Properties properties, String propertyId)
|
|
{
|
|
if ((properties == null) || (properties.getProperties() == null))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
PropertyData<?> property = properties.getProperties().get(propertyId);
|
|
if (!(property instanceof PropertyId))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return ((PropertyId) property).getFirstValue();
|
|
}
|
|
|
|
public String getNameProperty(Properties properties, String fallback)
|
|
{
|
|
String name = getStringProperty(properties, PropertyIds.NAME);
|
|
if ((name == null) || (name.trim().length() == 0))
|
|
{
|
|
if (fallback == null)
|
|
{
|
|
throw new CmisInvalidArgumentException("Property " + PropertyIds.NAME + " must be set!");
|
|
}
|
|
else
|
|
{
|
|
name = fallback;
|
|
}
|
|
}
|
|
|
|
return name;
|
|
}
|
|
|
|
public String getObjectTypeIdProperty(Properties properties)
|
|
{
|
|
String objectTypeId = getIdProperty(properties, PropertyIds.OBJECT_TYPE_ID);
|
|
if ((objectTypeId == null) || (objectTypeId.trim().length() == 0))
|
|
{
|
|
throw new CmisInvalidArgumentException("Property " + PropertyIds.OBJECT_TYPE_ID + " must be set!");
|
|
}
|
|
|
|
return objectTypeId;
|
|
}
|
|
|
|
public String getSourceIdProperty(Properties properties)
|
|
{
|
|
String id = getIdProperty(properties, PropertyIds.SOURCE_ID);
|
|
if ((id == null) || (id.trim().length() == 0))
|
|
{
|
|
throw new CmisInvalidArgumentException("Property " + PropertyIds.SOURCE_ID + " must be set!");
|
|
}
|
|
|
|
return id;
|
|
}
|
|
|
|
public String getTargetIdProperty(Properties properties)
|
|
{
|
|
String id = getIdProperty(properties, PropertyIds.TARGET_ID);
|
|
if ((id == null) || (id.trim().length() == 0))
|
|
{
|
|
throw new CmisInvalidArgumentException("Property " + PropertyIds.TARGET_ID + " must be set!");
|
|
}
|
|
|
|
return id;
|
|
}
|
|
|
|
/**
|
|
* Returns the repository info object.
|
|
*/
|
|
public RepositoryInfo getRepositoryInfo()
|
|
{
|
|
return createRepositoryInfo();
|
|
}
|
|
|
|
/**
|
|
* Returns the repository id.
|
|
*/
|
|
public String getRepositoryId()
|
|
{
|
|
return descriptorService.getCurrentRepositoryDescriptor().getId();
|
|
}
|
|
|
|
/**
|
|
* Creates the repository info object.
|
|
*/
|
|
private RepositoryInfo createRepositoryInfo()
|
|
{
|
|
Descriptor currentDescriptor = descriptorService.getCurrentRepositoryDescriptor();
|
|
|
|
// get change token
|
|
boolean auditEnabled = auditService.isAuditEnabled(CMIS_CHANGELOG_AUDIT_APPLICATION, "/"
|
|
+ CMIS_CHANGELOG_AUDIT_APPLICATION);
|
|
String latestChangeLogToken = null;
|
|
|
|
if (auditEnabled)
|
|
{
|
|
EntryIdCallback auditQueryCallback = new EntryIdCallback(false);
|
|
AuditQueryParameters params = new AuditQueryParameters();
|
|
params.setApplicationName(CMIS_CHANGELOG_AUDIT_APPLICATION);
|
|
params.setForward(false);
|
|
auditService.auditQuery(auditQueryCallback, params, 1);
|
|
latestChangeLogToken = auditQueryCallback.getEntryId();
|
|
}
|
|
|
|
// compile repository info
|
|
RepositoryInfoImpl ri = new RepositoryInfoImpl();
|
|
|
|
ri.setId(currentDescriptor.getId());
|
|
ri.setName(currentDescriptor.getName());
|
|
ri.setDescription(currentDescriptor.getName());
|
|
ri.setVendorName("Alfresco");
|
|
ri.setProductName("Alfresco Repository (" + currentDescriptor.getEdition() + ")");
|
|
ri.setProductVersion(currentDescriptor.getVersion());
|
|
ri.setRootFolder(getRootNodeRef().toString());
|
|
ri.setCmisVersionSupported("1.0");
|
|
|
|
ri.setChangesIncomplete(true);
|
|
ri.setChangesOnType(Arrays.asList(new BaseTypeId[] { BaseTypeId.CMIS_DOCUMENT, BaseTypeId.CMIS_FOLDER }));
|
|
ri.setLatestChangeLogToken(latestChangeLogToken);
|
|
ri.setPrincipalAnonymous(AuthenticationUtil.getGuestUserName());
|
|
ri.setPrincipalAnyone(PermissionService.ALL_AUTHORITIES);
|
|
|
|
RepositoryCapabilitiesImpl repCap = new RepositoryCapabilitiesImpl();
|
|
ri.setCapabilities(repCap);
|
|
|
|
repCap.setAllVersionsSearchable(false);
|
|
repCap.setCapabilityAcl(CapabilityAcl.MANAGE);
|
|
repCap.setCapabilityChanges(auditEnabled ? CapabilityChanges.OBJECTIDSONLY : CapabilityChanges.NONE);
|
|
repCap.setCapabilityContentStreamUpdates(CapabilityContentStreamUpdates.ANYTIME);
|
|
repCap.setCapabilityJoin(CapabilityJoin.NONE);
|
|
repCap.setCapabilityQuery(CapabilityQuery.BOTHCOMBINED);
|
|
repCap.setCapabilityRendition(CapabilityRenditions.READ);
|
|
repCap.setIsPwcSearchable(false);
|
|
repCap.setIsPwcUpdatable(true);
|
|
repCap.setSupportsGetDescendants(true);
|
|
repCap.setSupportsGetFolderTree(true);
|
|
repCap.setSupportsMultifiling(true);
|
|
repCap.setSupportsUnfiling(false);
|
|
repCap.setSupportsVersionSpecificFiling(false);
|
|
|
|
AclCapabilitiesDataImpl aclCap = new AclCapabilitiesDataImpl();
|
|
ri.setAclCapabilities(aclCap);
|
|
|
|
aclCap.setAclPropagation(AclPropagation.PROPAGATE);
|
|
aclCap.setSupportedPermissions(SupportedPermissions.BOTH);
|
|
aclCap.setPermissionDefinitionData(repositoryPermissions);
|
|
aclCap.setPermissionMappingData(permissionMappings);
|
|
|
|
return ri;
|
|
}
|
|
|
|
private List<PermissionDefinition> getRepositoryPermissions()
|
|
{
|
|
ArrayList<PermissionDefinition> result = new ArrayList<PermissionDefinition>();
|
|
|
|
Set<PermissionReference> all = permissionModelDao.getAllExposedPermissions();
|
|
for (PermissionReference pr : all)
|
|
{
|
|
result.add(createPermissionDefinition(pr));
|
|
}
|
|
|
|
PermissionReference allPermission = permissionModelDao.getPermissionReference(null,
|
|
PermissionService.ALL_PERMISSIONS);
|
|
result.add(createPermissionDefinition(allPermission));
|
|
|
|
PermissionDefinitionDataImpl cmisPermission;
|
|
|
|
cmisPermission = new PermissionDefinitionDataImpl();
|
|
cmisPermission.setPermission(BasicPermissions.READ);
|
|
cmisPermission.setDescription("CMIS Read");
|
|
result.add(cmisPermission);
|
|
|
|
cmisPermission = new PermissionDefinitionDataImpl();
|
|
cmisPermission.setPermission(BasicPermissions.WRITE);
|
|
cmisPermission.setDescription("CMIS Write");
|
|
result.add(cmisPermission);
|
|
|
|
cmisPermission = new PermissionDefinitionDataImpl();
|
|
cmisPermission.setPermission(BasicPermissions.ALL);
|
|
cmisPermission.setDescription("CMIS All");
|
|
result.add(cmisPermission);
|
|
|
|
return result;
|
|
}
|
|
|
|
private PermissionDefinition createPermissionDefinition(PermissionReference pr)
|
|
{
|
|
PermissionDefinitionDataImpl permission = new PermissionDefinitionDataImpl();
|
|
permission.setPermission(pr.getQName().toString() + "." + pr.getName());
|
|
permission.setDescription(permission.getId());
|
|
|
|
return permission;
|
|
}
|
|
|
|
private Map<String, PermissionMapping> getPermissionMappings()
|
|
{
|
|
Map<String, PermissionMapping> result = new HashMap<String, PermissionMapping>();
|
|
|
|
for (CMISAllowedActionEnum e : EnumSet.allOf(CMISAllowedActionEnum.class))
|
|
{
|
|
for (Map.Entry<String, List<String>> m : e.getPermissionMapping().entrySet())
|
|
{
|
|
PermissionMappingDataImpl mapping = new PermissionMappingDataImpl();
|
|
mapping.setKey(m.getKey());
|
|
mapping.setPermissions(m.getValue());
|
|
|
|
result.put(mapping.getKey(), mapping);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private CMISRenditionMapping getRenditionMapping()
|
|
{
|
|
CMISRenditionMapping renditionMapping = (CMISRenditionMapping)singletonCache.get(KEY_CMIS_RENDITION_MAPPING_NODEREF);
|
|
if (renditionMapping == null)
|
|
{
|
|
renditionMapping = new CMISRenditionMapping(nodeService, contentService, renditionService,
|
|
transactionService, kindToRenditionNames);
|
|
|
|
singletonCache.put(KEY_CMIS_RENDITION_MAPPING_NODEREF, renditionMapping);
|
|
}
|
|
return renditionMapping;
|
|
}
|
|
}
|