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
2730 lines
102 KiB
Java
2730 lines
102 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.BufferedInputStream;
|
|
import java.io.BufferedOutputStream;
|
|
import java.io.File;
|
|
import java.io.FileInputStream;
|
|
import java.io.FileOutputStream;
|
|
import java.io.InputStream;
|
|
import java.io.OutputStream;
|
|
import java.io.Serializable;
|
|
import java.math.BigInteger;
|
|
import java.nio.charset.Charset;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Collections;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
|
|
import javax.servlet.http.HttpServletRequest;
|
|
|
|
import net.sf.acegisecurity.Authentication;
|
|
|
|
import org.alfresco.cmis.CMISDictionaryModel;
|
|
import org.alfresco.cmis.CMISInvalidArgumentException;
|
|
import org.alfresco.model.ContentModel;
|
|
import org.alfresco.opencmis.dictionary.CMISNodeInfo;
|
|
import org.alfresco.opencmis.dictionary.CMISObjectVariant;
|
|
import org.alfresco.opencmis.dictionary.FolderTypeDefintionWrapper;
|
|
import org.alfresco.opencmis.dictionary.PropertyDefinitionWrapper;
|
|
import org.alfresco.opencmis.dictionary.TypeDefinitionWrapper;
|
|
import org.alfresco.query.PagingRequest;
|
|
import org.alfresco.query.PagingResults;
|
|
import org.alfresco.repo.content.encoding.ContentCharsetFinder;
|
|
import org.alfresco.repo.node.getchildren.GetChildrenCannedQuery;
|
|
import org.alfresco.repo.search.QueryParameterDefImpl;
|
|
import org.alfresco.repo.security.authentication.AuthenticationUtil;
|
|
import org.alfresco.repo.security.authentication.Authorization;
|
|
import org.alfresco.repo.version.VersionModel;
|
|
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
|
|
import org.alfresco.service.cmr.model.FileInfo;
|
|
import org.alfresco.service.cmr.model.FileNotFoundException;
|
|
import org.alfresco.service.cmr.repository.AssociationRef;
|
|
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
|
import org.alfresco.service.cmr.repository.ContentWriter;
|
|
import org.alfresco.service.cmr.repository.InvalidNodeRefException;
|
|
import org.alfresco.service.cmr.repository.NodeRef;
|
|
import org.alfresco.service.cmr.search.QueryParameterDefinition;
|
|
import org.alfresco.service.cmr.search.ResultSet;
|
|
import org.alfresco.service.cmr.search.SearchParameters;
|
|
import org.alfresco.service.cmr.search.SearchService;
|
|
import org.alfresco.service.cmr.version.Version;
|
|
import org.alfresco.service.cmr.version.VersionHistory;
|
|
import org.alfresco.service.cmr.version.VersionType;
|
|
import org.alfresco.service.namespace.NamespaceService;
|
|
import org.alfresco.service.namespace.QName;
|
|
import org.alfresco.service.namespace.RegexQNamePattern;
|
|
import org.alfresco.util.Pair;
|
|
import org.alfresco.util.TempFileProvider;
|
|
import org.apache.chemistry.opencmis.commons.PropertyIds;
|
|
import org.apache.chemistry.opencmis.commons.data.Acl;
|
|
import org.apache.chemistry.opencmis.commons.data.AllowableActions;
|
|
import org.apache.chemistry.opencmis.commons.data.ContentStream;
|
|
import org.apache.chemistry.opencmis.commons.data.ExtensionsData;
|
|
import org.apache.chemistry.opencmis.commons.data.FailedToDeleteData;
|
|
import org.apache.chemistry.opencmis.commons.data.ObjectData;
|
|
import org.apache.chemistry.opencmis.commons.data.ObjectInFolderContainer;
|
|
import org.apache.chemistry.opencmis.commons.data.ObjectInFolderData;
|
|
import org.apache.chemistry.opencmis.commons.data.ObjectInFolderList;
|
|
import org.apache.chemistry.opencmis.commons.data.ObjectList;
|
|
import org.apache.chemistry.opencmis.commons.data.ObjectParentData;
|
|
import org.apache.chemistry.opencmis.commons.data.Properties;
|
|
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.TypeDefinition;
|
|
import org.apache.chemistry.opencmis.commons.definitions.TypeDefinitionContainer;
|
|
import org.apache.chemistry.opencmis.commons.definitions.TypeDefinitionList;
|
|
import org.apache.chemistry.opencmis.commons.enums.AclPropagation;
|
|
import org.apache.chemistry.opencmis.commons.enums.BaseTypeId;
|
|
import org.apache.chemistry.opencmis.commons.enums.ContentStreamAllowed;
|
|
import org.apache.chemistry.opencmis.commons.enums.IncludeRelationships;
|
|
import org.apache.chemistry.opencmis.commons.enums.RelationshipDirection;
|
|
import org.apache.chemistry.opencmis.commons.enums.UnfileObject;
|
|
import org.apache.chemistry.opencmis.commons.enums.VersioningState;
|
|
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.CmisStorageException;
|
|
import org.apache.chemistry.opencmis.commons.exceptions.CmisStreamNotSupportedException;
|
|
import org.apache.chemistry.opencmis.commons.exceptions.CmisVersioningException;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlListImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.FailedToDeleteDataImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectInFolderContainerImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectInFolderDataImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectInFolderListImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectListImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectParentDataImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.TypeDefinitionContainerImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.TypeDefinitionListImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.server.AbstractCmisService;
|
|
import org.apache.chemistry.opencmis.commons.impl.server.ObjectInfoImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.server.RenditionInfoImpl;
|
|
import org.apache.chemistry.opencmis.commons.server.CallContext;
|
|
import org.apache.chemistry.opencmis.commons.server.ObjectInfo;
|
|
import org.apache.chemistry.opencmis.commons.server.RenditionInfo;
|
|
import org.apache.chemistry.opencmis.commons.spi.Holder;
|
|
import org.apache.commons.logging.Log;
|
|
import org.apache.commons.logging.LogFactory;
|
|
|
|
/**
|
|
* OpenCMIS service implementation
|
|
*
|
|
* @author florian.mueller
|
|
* @author Derek Hulley
|
|
* @since 4.0
|
|
*/
|
|
public class AlfrescoCmisServiceImpl extends AbstractCmisService implements AlfrescoCmisService
|
|
{
|
|
private static Log logger = LogFactory.getLog(AlfrescoCmisService.class);
|
|
|
|
private static final QName PARAM_PARENT = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "parent");
|
|
private static final QName PARAM_USERNAME = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "username");
|
|
private static final String LUCENE_QUERY_CHECKEDOUT = "+@cm\\:workingCopyOwner:${cm:username}";
|
|
private static final String LUCENE_QUERY_CHECKEDOUT_IN_FOLDER = "+@cm\\:workingCopyOwner:${cm:username} +PARENT:\"${cm:parent}\"";
|
|
|
|
private static final String MIN_FILTER = "cmis:name,cmis:baseTypeId,cmis:objectTypeId,"
|
|
+ "cmis:createdBy,cmis:creationDate,cmis:lastModifiedBy,cmis:lastModificationDate,"
|
|
+ "cmis:contentStreamLength,cmis:contentStreamMimeType,cmis:contentStreamFileName,"
|
|
+ "cmis:contentStreamId";
|
|
|
|
private CMISConnector connector;
|
|
private CallContext context;
|
|
private Authentication authentication;
|
|
private Map<String, CMISNodeInfo> nodeInfoMap;
|
|
private Map<String, ObjectInfo> objectInfoMap;
|
|
|
|
public AlfrescoCmisServiceImpl(CMISConnector connector)
|
|
{
|
|
this.connector = connector;
|
|
nodeInfoMap = new HashMap<String, CMISNodeInfo>();
|
|
objectInfoMap = new HashMap<String, ObjectInfo>();
|
|
}
|
|
|
|
@Override
|
|
public void beforeCall()
|
|
{
|
|
AuthenticationUtil.pushAuthentication();
|
|
if (authentication != null)
|
|
{
|
|
// Use the previously-obtained authentication
|
|
AuthenticationUtil.setFullAuthentication(authentication);
|
|
}
|
|
else
|
|
{
|
|
if (context == null)
|
|
{
|
|
// Service not opened, yet
|
|
return;
|
|
}
|
|
// Sticky sessions?
|
|
if (connector.openHttpSession())
|
|
{
|
|
// create a session -> set a cookie
|
|
// if the CMIS client supports cookies that might help in clustered environments
|
|
((HttpServletRequest) context.get(CallContext.HTTP_SERVLET_REQUEST)).getSession();
|
|
}
|
|
|
|
// Authenticate
|
|
if (authentication != null)
|
|
{
|
|
// We have already authenticated; just reuse the authentication
|
|
AuthenticationUtil.setFullAuthentication(authentication);
|
|
}
|
|
else
|
|
{
|
|
// We have to go to the repo and authenticate
|
|
String user = context.getUsername();
|
|
String password = context.getPassword();
|
|
Authorization auth = new Authorization(user, password);
|
|
if (auth.isTicket())
|
|
{
|
|
connector.getAuthenticationService().validate(auth.getTicket());
|
|
}
|
|
else
|
|
{
|
|
connector.getAuthenticationService().authenticate(auth.getUserName(), auth.getPasswordCharArray());
|
|
}
|
|
this.authentication = AuthenticationUtil.getFullAuthentication();
|
|
}
|
|
|
|
// // TODO: How is the proxy user working.
|
|
// // Until we know what it is meant to do, it's not available
|
|
// String currentUser = connector.getAuthenticationService().getCurrentUserName();
|
|
// String user = context.getUsername();
|
|
// String password = context.getPassword();
|
|
// if (currentUser != null && currentUser.equals(connector.getProxyUser()))
|
|
// {
|
|
// if (user != null && user.length() > 0)
|
|
// {
|
|
// AuthenticationUtil.setFullyAuthenticatedUser(user);
|
|
// }
|
|
// }
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void afterCall()
|
|
{
|
|
AuthenticationUtil.popAuthentication();
|
|
}
|
|
|
|
@Override
|
|
public void open(CallContext context)
|
|
{
|
|
this.context = context;
|
|
}
|
|
|
|
@Override
|
|
public void close()
|
|
{
|
|
// Put these resources on the transactions
|
|
context = null;
|
|
authentication = null;
|
|
nodeInfoMap.clear();
|
|
objectInfoMap.clear();
|
|
}
|
|
|
|
protected CMISNodeInfoImpl createNodeInfo(NodeRef nodeRef)
|
|
{
|
|
return createNodeInfo(nodeRef, null);
|
|
}
|
|
|
|
protected CMISNodeInfoImpl createNodeInfo(NodeRef nodeRef, VersionHistory versionHistory)
|
|
{
|
|
CMISNodeInfoImpl result = connector.createNodeInfo(nodeRef, versionHistory);
|
|
nodeInfoMap.put(result.getObjectId(), result);
|
|
|
|
return result;
|
|
}
|
|
|
|
protected CMISNodeInfo createNodeInfo(AssociationRef assocRef)
|
|
{
|
|
CMISNodeInfoImpl result = connector.createNodeInfo(assocRef);
|
|
nodeInfoMap.put(result.getObjectId(), result);
|
|
|
|
return result;
|
|
}
|
|
|
|
protected CMISNodeInfo getOrCreateNodeInfo(String objectId)
|
|
{
|
|
CMISNodeInfo result = nodeInfoMap.get(objectId);
|
|
if (result == null)
|
|
{
|
|
result = connector.createNodeInfo(objectId);
|
|
nodeInfoMap.put(objectId, result);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
protected CMISNodeInfo getOrCreateNodeInfo(String objectId, String what)
|
|
{
|
|
CMISNodeInfo result = getOrCreateNodeInfo(objectId);
|
|
if (result instanceof CMISNodeInfoImpl)
|
|
{
|
|
((CMISNodeInfoImpl) result).checkIfUseful(what);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
protected CMISNodeInfo getOrCreateFolderInfo(String folderId, String what)
|
|
{
|
|
CMISNodeInfo result = getOrCreateNodeInfo(folderId);
|
|
if (result instanceof CMISNodeInfoImpl)
|
|
{
|
|
((CMISNodeInfoImpl) result).checkIfFolder(what);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
protected CMISNodeInfo addNodeInfo(CMISNodeInfo info)
|
|
{
|
|
nodeInfoMap.put(info.getObjectId(), info);
|
|
|
|
return info;
|
|
}
|
|
|
|
// --- repository service ---
|
|
|
|
@Override
|
|
public List<RepositoryInfo> getRepositoryInfos(ExtensionsData extension)
|
|
{
|
|
return Collections.singletonList(connector.getRepositoryInfo());
|
|
}
|
|
|
|
@Override
|
|
public RepositoryInfo getRepositoryInfo(String repositoryId, ExtensionsData extension)
|
|
{
|
|
checkRepositoryId(repositoryId);
|
|
|
|
return connector.getRepositoryInfo();
|
|
}
|
|
|
|
@Override
|
|
public TypeDefinitionList getTypeChildren(
|
|
String repositoryId, String typeId, Boolean includePropertyDefinitions,
|
|
BigInteger maxItems, BigInteger skipCount, ExtensionsData extension)
|
|
{
|
|
checkRepositoryId(repositoryId);
|
|
|
|
// convert BigIntegers to int
|
|
int max = (maxItems == null ? Integer.MAX_VALUE : maxItems.intValue());
|
|
int skip = (skipCount == null || skipCount.intValue() < 0 ? 0 : skipCount.intValue());
|
|
|
|
// set up the result
|
|
TypeDefinitionListImpl result = new TypeDefinitionListImpl();
|
|
List<TypeDefinition> list = new ArrayList<TypeDefinition>();
|
|
result.setList(list);
|
|
|
|
// get the types from the dictionary
|
|
List<TypeDefinitionWrapper> childrenList;
|
|
if (typeId == null)
|
|
{
|
|
childrenList = connector.getOpenCMISDictionaryService().getBaseTypes();
|
|
}
|
|
else
|
|
{
|
|
TypeDefinitionWrapper tdw = connector.getOpenCMISDictionaryService().findType(typeId);
|
|
if (tdw == null)
|
|
{
|
|
throw new CmisObjectNotFoundException("Type '" + typeId + "' is unknown!");
|
|
}
|
|
childrenList = tdw.getChildren();
|
|
}
|
|
|
|
// create result
|
|
if (max > 0)
|
|
{
|
|
int lastIndex = (max + skip > childrenList.size() ? childrenList.size() : max + skip) - 1;
|
|
for (int i = skip; i <= lastIndex; i++)
|
|
{
|
|
list.add(childrenList.get(i).getTypeDefinition(includePropertyDefinitions));
|
|
}
|
|
}
|
|
|
|
result.setHasMoreItems(childrenList.size() - skip > result.getList().size());
|
|
result.setNumItems(BigInteger.valueOf(childrenList.size()));
|
|
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public TypeDefinition getTypeDefinition(String repositoryId, String typeId, ExtensionsData extension)
|
|
{
|
|
checkRepositoryId(repositoryId);
|
|
|
|
// find the type
|
|
TypeDefinitionWrapper tdw = connector.getOpenCMISDictionaryService().findType(typeId);
|
|
if (tdw == null)
|
|
{
|
|
throw new CmisObjectNotFoundException("Type '" + typeId + "' is unknown!");
|
|
}
|
|
|
|
// return type definition
|
|
return tdw.getTypeDefinition(true);
|
|
}
|
|
|
|
@Override
|
|
public List<TypeDefinitionContainer> getTypeDescendants(
|
|
String repositoryId, String typeId, BigInteger depth,
|
|
Boolean includePropertyDefinitions, ExtensionsData extension)
|
|
{
|
|
checkRepositoryId(repositoryId);
|
|
|
|
List<TypeDefinitionContainer> result = new ArrayList<TypeDefinitionContainer>();
|
|
|
|
// check depth
|
|
int d = (depth == null ? -1 : depth.intValue());
|
|
if (d == 0)
|
|
{
|
|
throw new CmisInvalidArgumentException("Depth must not be 0!");
|
|
}
|
|
|
|
if (typeId == null)
|
|
{
|
|
for (TypeDefinitionWrapper tdw : connector.getOpenCMISDictionaryService().getBaseTypes())
|
|
{
|
|
result.add(getTypesDescendants(d, tdw, includePropertyDefinitions));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TypeDefinitionWrapper tdw = connector.getOpenCMISDictionaryService().findType(typeId);
|
|
if (tdw == null)
|
|
{
|
|
throw new CmisObjectNotFoundException("Type '" + typeId + "' is unknown!");
|
|
}
|
|
|
|
if (tdw.getChildren() != null)
|
|
{
|
|
for (TypeDefinitionWrapper child : tdw.getChildren())
|
|
{
|
|
result.add(getTypesDescendants(d, child, includePropertyDefinitions));
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Gathers the type descendants tree.
|
|
*/
|
|
private TypeDefinitionContainer getTypesDescendants(
|
|
int depth, TypeDefinitionWrapper tdw, boolean includePropertyDefinitions)
|
|
{
|
|
TypeDefinitionContainerImpl result = new TypeDefinitionContainerImpl();
|
|
|
|
result.setTypeDefinition(tdw.getTypeDefinition(includePropertyDefinitions));
|
|
|
|
if (depth != 0)
|
|
{
|
|
if (tdw.getChildren() != null)
|
|
{
|
|
result.setChildren(new ArrayList<TypeDefinitionContainer>());
|
|
for (TypeDefinitionWrapper tdc : tdw.getChildren())
|
|
{
|
|
result.getChildren().add(
|
|
getTypesDescendants(depth < 0 ? -1 : depth - 1, tdc, includePropertyDefinitions));
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// --- navigation service ---
|
|
|
|
@Override
|
|
public ObjectInFolderList getChildren(
|
|
String repositoryId, String folderId, String filter, String orderBy,
|
|
Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter,
|
|
Boolean includePathSegment, BigInteger maxItems, BigInteger skipCount, ExtensionsData extension)
|
|
{
|
|
long start = System.currentTimeMillis();
|
|
|
|
checkRepositoryId(repositoryId);
|
|
|
|
// convert BigIntegers to int
|
|
int max = (maxItems == null ? Integer.MAX_VALUE : maxItems.intValue());
|
|
int skip = (skipCount == null || skipCount.intValue() < 0 ? 0 : skipCount.intValue());
|
|
|
|
ObjectInFolderListImpl result = new ObjectInFolderListImpl();
|
|
List<ObjectInFolderData> list = new ArrayList<ObjectInFolderData>();
|
|
result.setObjects(list);
|
|
|
|
// get the children references
|
|
NodeRef folderNodeRef = getOrCreateFolderInfo(folderId, "Folder").getNodeRef();
|
|
|
|
// convert orderBy to sortProps
|
|
List<Pair<QName, Boolean>> sortProps = null;
|
|
if (orderBy != null)
|
|
{
|
|
sortProps = new ArrayList<Pair<QName, Boolean>>(1);
|
|
|
|
String[] parts = orderBy.split(",");
|
|
int len = parts.length;
|
|
final int origLen = len;
|
|
|
|
if (origLen > 0)
|
|
{
|
|
int maxSortProps = GetChildrenCannedQuery.MAX_FILTER_SORT_PROPS;
|
|
if (len > maxSortProps)
|
|
{
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
logger.debug(
|
|
"Too many sort properties in 'orderBy' - ignore those above max (max="
|
|
+ maxSortProps + ",actual=" + len + ")");
|
|
}
|
|
len = maxSortProps;
|
|
}
|
|
for (int i = 0; i < len; i++)
|
|
{
|
|
String[] sort = parts[i].split(" +");
|
|
|
|
if (sort.length > 0)
|
|
{
|
|
PropertyDefinitionWrapper propDef = connector.getOpenCMISDictionaryService()
|
|
.findPropertyByQueryName(sort[0]);
|
|
if (propDef != null)
|
|
{
|
|
QName sortProp = null;
|
|
if (propDef.getPropertyId().equals(CMISDictionaryModel.PROP_BASE_TYPE_ID))
|
|
{
|
|
// special-case (see also ALF-13968) - for getChildren, using "cmis:baseTypeId" allows sorting of folders first and vice-versa (cmis:folder <-> cmis:document)
|
|
sortProp = GetChildrenCannedQuery.SORT_QNAME_NODE_IS_FOLDER;
|
|
}
|
|
else
|
|
{
|
|
sortProp = propDef.getPropertyAccessor().getMappedProperty();
|
|
}
|
|
|
|
if (sortProp != null)
|
|
{
|
|
boolean sortAsc = (sort.length == 1) || sort[1].equalsIgnoreCase("asc");
|
|
sortProps.add(new Pair<QName, Boolean>(sortProp, sortAsc));
|
|
}
|
|
else
|
|
{
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
logger.debug("Ignore sort property '" + sort[0] + " - mapping not found");
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
logger.debug("Ignore sort property '" + sort[0] + " - query name not found");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (sortProps.size() < origLen)
|
|
{
|
|
logger.warn("Sort properties trimmed - either too many and/or not found: \n" + " orig: " + orderBy
|
|
+ "\n" + " final: " + sortProps);
|
|
}
|
|
}
|
|
|
|
PagingRequest pageRequest = new PagingRequest(skip, max, null);
|
|
pageRequest.setRequestTotalCountMax(skip + 10000); // TODO make this optional/configurable
|
|
// - affects whether numItems may be returned
|
|
|
|
PagingResults<FileInfo> pageOfNodeInfos = connector.getFileFolderService().list(
|
|
folderNodeRef, true, true,
|
|
null, sortProps, pageRequest);
|
|
|
|
if (max > 0)
|
|
{
|
|
for (FileInfo child : pageOfNodeInfos.getPage())
|
|
{
|
|
try
|
|
{
|
|
// create a child CMIS object
|
|
CMISNodeInfo ni = createNodeInfo(child.getNodeRef());
|
|
if (getObjectInfo(repositoryId, ni.getObjectId())==null)
|
|
{
|
|
// ignore invalid children
|
|
continue;
|
|
}
|
|
ObjectData object = connector.createCMISObject(ni, child, filter, includeAllowableActions,
|
|
includeRelationships, renditionFilter, false, false);
|
|
|
|
if (context.isObjectInfoRequired())
|
|
{
|
|
getObjectInfo(repositoryId, ni.getObjectId(), includeRelationships);
|
|
}
|
|
|
|
ObjectInFolderDataImpl childData = new ObjectInFolderDataImpl();
|
|
childData.setObject(object);
|
|
|
|
// include path segment
|
|
if (includePathSegment)
|
|
{
|
|
childData.setPathSegment(child.getName());
|
|
}
|
|
|
|
// add it
|
|
list.add(childData);
|
|
}
|
|
catch (InvalidNodeRefException e)
|
|
{
|
|
// ignore invalid children
|
|
}
|
|
catch(CmisObjectNotFoundException e)
|
|
{
|
|
// ignore objects that have not been found (perhaps because their type is unknown to CMIS)
|
|
}
|
|
}
|
|
}
|
|
|
|
// has more ?
|
|
result.setHasMoreItems(pageOfNodeInfos.hasMoreItems());
|
|
|
|
// total count ?
|
|
Pair<Integer, Integer> totalCounts = pageOfNodeInfos.getTotalResultCount();
|
|
if (totalCounts != null)
|
|
{
|
|
Integer totalCountLower = totalCounts.getFirst();
|
|
Integer totalCountUpper = totalCounts.getSecond();
|
|
if ((totalCountLower != null) && (totalCountLower.equals(totalCountUpper)))
|
|
{
|
|
result.setNumItems(BigInteger.valueOf(totalCountLower));
|
|
}
|
|
}
|
|
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
logger.debug("getChildren: " + list.size() + " in " + (System.currentTimeMillis() - start) + " msecs");
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public List<ObjectInFolderContainer> getDescendants(
|
|
String repositoryId, String folderId, BigInteger depth,
|
|
String filter, Boolean includeAllowableActions, IncludeRelationships includeRelationships,
|
|
String renditionFilter, Boolean includePathSegment, ExtensionsData extension)
|
|
{
|
|
checkRepositoryId(repositoryId);
|
|
|
|
List<ObjectInFolderContainer> result = new ArrayList<ObjectInFolderContainer>();
|
|
|
|
getDescendantsTree(
|
|
repositoryId,
|
|
getOrCreateFolderInfo(folderId, "Folder").getNodeRef(),
|
|
depth.intValue(),
|
|
filter,
|
|
includeAllowableActions, includeRelationships, renditionFilter, includePathSegment, false,
|
|
result);
|
|
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public List<ObjectInFolderContainer> getFolderTree(
|
|
String repositoryId, String folderId, BigInteger depth,
|
|
String filter, Boolean includeAllowableActions, IncludeRelationships includeRelationships,
|
|
String renditionFilter, Boolean includePathSegment, ExtensionsData extension)
|
|
{
|
|
checkRepositoryId(repositoryId);
|
|
|
|
List<ObjectInFolderContainer> result = new ArrayList<ObjectInFolderContainer>();
|
|
|
|
getDescendantsTree(
|
|
repositoryId,
|
|
getOrCreateFolderInfo(folderId, "Folder").getNodeRef(),
|
|
depth.intValue(),
|
|
filter, includeAllowableActions, includeRelationships, renditionFilter, includePathSegment, true,
|
|
result);
|
|
|
|
return result;
|
|
}
|
|
|
|
private void getDescendantsTree(
|
|
String repositoryId, NodeRef folderNodeRef, int depth, String filter,
|
|
Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter,
|
|
Boolean includePathSegment, boolean foldersOnly, List<ObjectInFolderContainer> list)
|
|
{
|
|
// get the children references
|
|
List<ChildAssociationRef> childrenList = connector.getNodeService().getChildAssocs(folderNodeRef);
|
|
for (ChildAssociationRef child : childrenList)
|
|
{
|
|
try
|
|
{
|
|
TypeDefinitionWrapper type = connector.getType(child.getChildRef());
|
|
if (type == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
boolean isFolder = (type instanceof FolderTypeDefintionWrapper);
|
|
|
|
if (foldersOnly && !isFolder)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if(isFolder && type.getAlfrescoClass().equals(ContentModel.TYPE_SYSTEM_FOLDER))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// create a child CMIS object
|
|
ObjectInFolderDataImpl object = new ObjectInFolderDataImpl();
|
|
CMISNodeInfo ni = createNodeInfo(child.getChildRef());
|
|
object.setObject(connector.createCMISObject(
|
|
ni, filter, includeAllowableActions, includeRelationships,
|
|
renditionFilter, false, false));
|
|
if (context.isObjectInfoRequired())
|
|
{
|
|
getObjectInfo(repositoryId, ni.getObjectId(), includeRelationships);
|
|
}
|
|
|
|
if (includePathSegment)
|
|
{
|
|
object.setPathSegment(connector.getName(child.getChildRef()));
|
|
}
|
|
|
|
// create the container
|
|
ObjectInFolderContainerImpl container = new ObjectInFolderContainerImpl();
|
|
container.setObject(object);
|
|
|
|
if ((depth != 1) && isFolder)
|
|
{
|
|
container.setChildren(new ArrayList<ObjectInFolderContainer>());
|
|
getDescendantsTree(
|
|
repositoryId,
|
|
child.getChildRef(),
|
|
depth - 1, filter, includeAllowableActions,
|
|
includeRelationships, renditionFilter, includePathSegment, foldersOnly,
|
|
container.getChildren());
|
|
}
|
|
|
|
// add it
|
|
list.add(container);
|
|
}
|
|
catch (InvalidNodeRefException e)
|
|
{
|
|
// ignore invalid children
|
|
}
|
|
catch(CmisObjectNotFoundException e)
|
|
{
|
|
// ignore objects that have not been found (perhaps because their type is unknown to CMIS)
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public ObjectData getFolderParent(String repositoryId, String folderId, String filter, ExtensionsData extension)
|
|
{
|
|
checkRepositoryId(repositoryId);
|
|
|
|
// get the node ref
|
|
CMISNodeInfo info = getOrCreateFolderInfo(folderId, "Folder");
|
|
|
|
// the root folder has no parent
|
|
if (info.isRootFolder())
|
|
{
|
|
throw new CmisInvalidArgumentException("Root folder has no parent!");
|
|
}
|
|
|
|
// get the parent
|
|
List<CMISNodeInfo> parentInfos = info.getParents();
|
|
if (parentInfos.isEmpty())
|
|
{
|
|
throw new CmisRuntimeException("Folder has no parent and is not the root folder?!");
|
|
}
|
|
|
|
CMISNodeInfo parentInfo = addNodeInfo(parentInfos.get(0));
|
|
|
|
ObjectData result = connector.createCMISObject(
|
|
parentInfo, filter, false, IncludeRelationships.NONE,
|
|
CMISConnector.RENDITION_NONE, false, false);
|
|
if (context.isObjectInfoRequired())
|
|
{
|
|
getObjectInfo(
|
|
repositoryId,
|
|
parentInfo.getObjectId(),
|
|
IncludeRelationships.NONE);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public List<ObjectParentData> getObjectParents(
|
|
String repositoryId, String objectId, String filter,
|
|
Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter,
|
|
Boolean includeRelativePathSegment, ExtensionsData extension)
|
|
{
|
|
checkRepositoryId(repositoryId);
|
|
|
|
List<ObjectParentData> result = new ArrayList<ObjectParentData>();
|
|
|
|
// what kind of object is it?
|
|
CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object");
|
|
|
|
// relationships are not filed
|
|
if (info.isRelationship())
|
|
{
|
|
throw new CmisConstraintException("Relationships are not fileable!");
|
|
}
|
|
|
|
if (info.isFolder() && !info.isRootFolder())
|
|
{
|
|
List<CMISNodeInfo> parentInfos = info.getParents();
|
|
if (!parentInfos.isEmpty())
|
|
{
|
|
CMISNodeInfo parentInfo = addNodeInfo(parentInfos.get(0));
|
|
|
|
ObjectData object = connector.createCMISObject(
|
|
parentInfo, filter, includeAllowableActions,
|
|
includeRelationships, renditionFilter, false, false);
|
|
if (context.isObjectInfoRequired())
|
|
{
|
|
getObjectInfo(repositoryId, object.getId(), includeRelationships);
|
|
}
|
|
|
|
ObjectParentDataImpl objectParent = new ObjectParentDataImpl();
|
|
objectParent.setObject(object);
|
|
|
|
// include relative path segment
|
|
if (includeRelativePathSegment)
|
|
{
|
|
objectParent.setRelativePathSegment(info.getName());
|
|
}
|
|
|
|
result.add(objectParent);
|
|
}
|
|
}
|
|
else if (info.isCurrentVersion() || info.isPWC())
|
|
{
|
|
List<CMISNodeInfo> parentInfos = info.getParents();
|
|
for (CMISNodeInfo parentInfo : parentInfos)
|
|
{
|
|
addNodeInfo(parentInfo);
|
|
|
|
ObjectData object = connector.createCMISObject(
|
|
parentInfo, filter, includeAllowableActions,
|
|
includeRelationships, renditionFilter, false, false);
|
|
if (context.isObjectInfoRequired())
|
|
{
|
|
getObjectInfo(repositoryId, object.getId(), includeRelationships);
|
|
}
|
|
|
|
ObjectParentDataImpl objectParent = new ObjectParentDataImpl();
|
|
objectParent.setObject(object);
|
|
|
|
// include relative path segment
|
|
if (includeRelativePathSegment)
|
|
{
|
|
objectParent.setRelativePathSegment(info.getName());
|
|
}
|
|
|
|
result.add(objectParent);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public ObjectList getCheckedOutDocs(
|
|
String repositoryId, String folderId, String filter, String orderBy,
|
|
Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter,
|
|
BigInteger maxItems, BigInteger skipCount, ExtensionsData extension)
|
|
{
|
|
checkRepositoryId(repositoryId);
|
|
|
|
// convert BigIntegers to int
|
|
int max = (maxItems == null ? Integer.MAX_VALUE : maxItems.intValue());
|
|
int skip = (skipCount == null || skipCount.intValue() < 0 ? 0 : skipCount.intValue());
|
|
|
|
// prepare query
|
|
SearchParameters params = new SearchParameters();
|
|
params.setLanguage(SearchService.LANGUAGE_LUCENE);
|
|
QueryParameterDefinition usernameDef = new QueryParameterDefImpl(
|
|
PARAM_USERNAME,
|
|
connector.getDictionaryService().getDataType(DataTypeDefinition.TEXT),
|
|
true,
|
|
AuthenticationUtil.getFullyAuthenticatedUser());
|
|
params.addQueryParameterDefinition(usernameDef);
|
|
|
|
if (folderId == null)
|
|
{
|
|
params.setQuery(LUCENE_QUERY_CHECKEDOUT);
|
|
params.addStore(connector.getRootStoreRef());
|
|
}
|
|
else
|
|
{
|
|
CMISNodeInfo folderInfo = getOrCreateFolderInfo(folderId, "Folder");
|
|
|
|
params.setQuery(LUCENE_QUERY_CHECKEDOUT_IN_FOLDER);
|
|
params.addStore(folderInfo.getNodeRef().getStoreRef());
|
|
QueryParameterDefinition parentDef = new QueryParameterDefImpl(
|
|
PARAM_PARENT,
|
|
connector.getDictionaryService().getDataType(DataTypeDefinition.NODE_REF),
|
|
true,
|
|
folderInfo.getNodeRef().toString());
|
|
params.addQueryParameterDefinition(parentDef);
|
|
}
|
|
|
|
// set up order
|
|
if (orderBy != null)
|
|
{
|
|
String[] parts = orderBy.split(",");
|
|
for (int i = 0; i < parts.length; i++)
|
|
{
|
|
String[] sort = parts[i].split(" +");
|
|
|
|
if (sort.length < 1)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
PropertyDefinitionWrapper propDef = connector.getOpenCMISDictionaryService().findPropertyByQueryName(sort[0]);
|
|
if (propDef != null)
|
|
{
|
|
if (propDef.getPropertyDefinition().isOrderable())
|
|
{
|
|
QName sortProp = propDef.getPropertyAccessor().getMappedProperty();
|
|
if (sortProp != null)
|
|
{
|
|
boolean sortAsc = (sort.length == 1) || sort[1].equalsIgnoreCase("asc");
|
|
params.addSort(propDef.getPropertyLuceneBuilder().getLuceneFieldName(), sortAsc);
|
|
}
|
|
else
|
|
{
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
logger.debug("Ignore sort property '" + sort[0] + " - mapping not found");
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
logger.debug("Ignore sort property '" + sort[0] + " - not orderable");
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
logger.debug("Ignore sort property '" + sort[0] + " - query name not found");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// execute query
|
|
ResultSet resultSet = null;
|
|
List<NodeRef> nodeRefs;
|
|
try
|
|
{
|
|
resultSet = connector.getSearchService().query(params);
|
|
nodeRefs = resultSet.getNodeRefs();
|
|
}
|
|
finally
|
|
{
|
|
if (resultSet != null)
|
|
{
|
|
resultSet.close();
|
|
}
|
|
}
|
|
|
|
// collect results
|
|
ObjectListImpl result = new ObjectListImpl();
|
|
List<ObjectData> list = new ArrayList<ObjectData>();
|
|
result.setObjects(list);
|
|
|
|
int skipCounter = skip;
|
|
if (max > 0)
|
|
{
|
|
for (NodeRef nodeRef : nodeRefs)
|
|
{
|
|
if (skipCounter > 0)
|
|
{
|
|
skipCounter--;
|
|
continue;
|
|
}
|
|
|
|
if (list.size() == max)
|
|
{
|
|
break;
|
|
}
|
|
|
|
try
|
|
{
|
|
// create a CMIS object
|
|
CMISNodeInfo ni = createNodeInfo(nodeRef);
|
|
ObjectData object = connector.createCMISObject(
|
|
ni, filter, includeAllowableActions,
|
|
includeRelationships, renditionFilter, false, false);
|
|
|
|
if (context.isObjectInfoRequired())
|
|
{
|
|
getObjectInfo(repositoryId, ni.getObjectId(), includeRelationships);
|
|
}
|
|
|
|
// add it
|
|
list.add(object);
|
|
}
|
|
catch (InvalidNodeRefException e)
|
|
{
|
|
// ignore invalid objects
|
|
}
|
|
catch(CmisObjectNotFoundException e)
|
|
{
|
|
// ignore objects that have not been found (perhaps because their type is unknown to CMIS)
|
|
}
|
|
}
|
|
}
|
|
|
|
// has more ?
|
|
result.setHasMoreItems(nodeRefs.size() - skip > list.size());
|
|
|
|
return result;
|
|
}
|
|
|
|
// --- object service ---
|
|
|
|
@Override
|
|
public String create(
|
|
String repositoryId, Properties properties, String folderId, ContentStream contentStream,
|
|
VersioningState versioningState, List<String> policies, ExtensionsData extension)
|
|
{
|
|
checkRepositoryId(repositoryId);
|
|
|
|
// check properties
|
|
if (properties == null || properties.getProperties() == null)
|
|
{
|
|
throw new CmisInvalidArgumentException("Properties must be set!");
|
|
}
|
|
|
|
// get the type
|
|
String objectTypeId = connector.getObjectTypeIdProperty(properties);
|
|
|
|
// find the type
|
|
TypeDefinitionWrapper type = connector.getOpenCMISDictionaryService().findType(objectTypeId);
|
|
if (type == null)
|
|
{
|
|
throw new CmisInvalidArgumentException("Type '" + objectTypeId + "' is unknown!");
|
|
}
|
|
|
|
// create object
|
|
String newId = null;
|
|
switch (type.getBaseTypeId())
|
|
{
|
|
case CMIS_DOCUMENT:
|
|
newId = createDocument(repositoryId, properties, folderId, contentStream, versioningState, policies, null,
|
|
null, extension);
|
|
break;
|
|
case CMIS_FOLDER:
|
|
newId = createFolder(repositoryId, properties, folderId, policies, null, null, extension);
|
|
break;
|
|
case CMIS_POLICY:
|
|
newId = createPolicy(repositoryId, properties, folderId, policies, null, null, extension);
|
|
break;
|
|
}
|
|
|
|
// check new object id
|
|
if (newId == null)
|
|
{
|
|
throw new CmisRuntimeException("Creation failed!");
|
|
}
|
|
|
|
if (context.isObjectInfoRequired())
|
|
{
|
|
try
|
|
{
|
|
getObjectInfo(repositoryId, newId, "*", IncludeRelationships.NONE);
|
|
}
|
|
catch (InvalidNodeRefException e)
|
|
{
|
|
throw new CmisRuntimeException("Creation failed! New object not found!");
|
|
}
|
|
}
|
|
|
|
// return the new object id
|
|
return newId;
|
|
}
|
|
|
|
@Override
|
|
public String createFolder(String repositoryId, final Properties properties, String folderId,
|
|
final List<String> policies, final Acl addAces, final Acl removeAces, ExtensionsData extension)
|
|
{
|
|
checkRepositoryId(repositoryId);
|
|
|
|
// get the parent folder node ref
|
|
final CMISNodeInfo parentInfo = getOrCreateFolderInfo(folderId, "Folder");
|
|
|
|
// get name and type
|
|
final String name = connector.getNameProperty(properties, null);
|
|
final String objectTypeId = connector.getObjectTypeIdProperty(properties);
|
|
final TypeDefinitionWrapper type = connector.getTypeForCreate(objectTypeId, BaseTypeId.CMIS_FOLDER);
|
|
|
|
connector.checkChildObjectType(parentInfo, type.getTypeId());
|
|
|
|
// run transaction
|
|
FileInfo fileInfo = connector.getFileFolderService().create(
|
|
parentInfo.getNodeRef(), name, type.getAlfrescoClass());
|
|
NodeRef nodeRef = fileInfo.getNodeRef();
|
|
|
|
|
|
connector.setProperties(nodeRef, type, properties, new String[] { PropertyIds.NAME, PropertyIds.OBJECT_TYPE_ID });
|
|
connector.applyPolicies(nodeRef, type, policies);
|
|
connector.applyACL(nodeRef, type, addAces, removeAces);
|
|
|
|
connector.getActivityPoster().postFileFolderAdded(fileInfo);
|
|
|
|
return nodeRef.toString();
|
|
}
|
|
|
|
private String stripEncoding(String mimeType)
|
|
{
|
|
String ret = mimeType;
|
|
|
|
int idx = mimeType.indexOf(";");
|
|
if(idx != -1)
|
|
{
|
|
ret = mimeType.substring(0, idx);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
@Override
|
|
public String createDocument(
|
|
String repositoryId, final Properties properties, String folderId,
|
|
final ContentStream contentStream, final VersioningState versioningState, final List<String> policies,
|
|
final Acl addAces, final Acl removeAces, ExtensionsData extension)
|
|
{
|
|
checkRepositoryId(repositoryId);
|
|
|
|
// get the parent folder node ref
|
|
final CMISNodeInfo parentInfo = getOrCreateFolderInfo(folderId, "Parent folder");
|
|
|
|
// get name and type
|
|
final String name = connector.getNameProperty(properties, null);
|
|
final String objectTypeId = connector.getObjectTypeIdProperty(properties);
|
|
final TypeDefinitionWrapper type = connector.getTypeForCreate(objectTypeId, BaseTypeId.CMIS_DOCUMENT);
|
|
|
|
connector.checkChildObjectType(parentInfo, type.getTypeId());
|
|
|
|
DocumentTypeDefinition docType = (DocumentTypeDefinition) type.getTypeDefinition(false);
|
|
|
|
if ((docType.getContentStreamAllowed() == ContentStreamAllowed.NOTALLOWED) && (contentStream != null))
|
|
{
|
|
throw new CmisConstraintException("This document type does not support content!");
|
|
}
|
|
|
|
if ((docType.getContentStreamAllowed() == ContentStreamAllowed.REQUIRED) && (contentStream == null))
|
|
{
|
|
throw new CmisConstraintException("This document type does requires content!");
|
|
}
|
|
|
|
if (docType.isVersionable() && (versioningState == VersioningState.NONE))
|
|
{
|
|
throw new CmisConstraintException("This document type is versionable!");
|
|
}
|
|
|
|
if (!docType.isVersionable() && (versioningState != VersioningState.NONE))
|
|
{
|
|
throw new CmisConstraintException("This document type is not versionable!");
|
|
}
|
|
|
|
// copy stream to temp file
|
|
// OpenCMIS does this for us ....
|
|
final File tempFile = copyToTempFile(contentStream);
|
|
final Charset encoding = (tempFile == null ? null : getEncoding(tempFile, contentStream.getMimeType()));
|
|
|
|
FileInfo fileInfo = connector.getFileFolderService().create(
|
|
parentInfo.getNodeRef(), name, type.getAlfrescoClass());
|
|
NodeRef nodeRef = fileInfo.getNodeRef();
|
|
connector.setProperties(nodeRef, type, properties, new String[] { PropertyIds.NAME, PropertyIds.OBJECT_TYPE_ID });
|
|
connector.applyPolicies(nodeRef, type, policies);
|
|
connector.applyACL(nodeRef, type, addAces, removeAces);
|
|
|
|
// handle content
|
|
if (contentStream != null)
|
|
{
|
|
// write content
|
|
ContentWriter writer = connector.getFileFolderService().getWriter(nodeRef);
|
|
writer.setMimetype(stripEncoding(contentStream.getMimeType()));
|
|
writer.setEncoding(encoding.name());
|
|
writer.putContent(tempFile);
|
|
}
|
|
|
|
connector.applyVersioningState(nodeRef, versioningState);
|
|
|
|
removeTempFile(tempFile);
|
|
|
|
String objectId = connector.createObjectId(nodeRef);
|
|
|
|
connector.getActivityPoster().postFileFolderAdded(fileInfo);
|
|
|
|
return objectId;
|
|
}
|
|
|
|
@Override
|
|
public String createDocumentFromSource(
|
|
String repositoryId, String sourceId, final Properties properties,
|
|
String folderId, final VersioningState versioningState, final List<String> policies, final Acl addAces,
|
|
final Acl removeAces, ExtensionsData extension)
|
|
{
|
|
checkRepositoryId(repositoryId);
|
|
|
|
// get the parent folder node ref
|
|
final CMISNodeInfo parentInfo = getOrCreateFolderInfo(folderId, "Parent folder");
|
|
|
|
// get source
|
|
CMISNodeInfo info = getOrCreateNodeInfo(sourceId, "Source");
|
|
|
|
// check source
|
|
if (info.isVariant(CMISObjectVariant.ASSOC))
|
|
{
|
|
throw new CmisConstraintException("Source object is not a document!");
|
|
}
|
|
|
|
final NodeRef sourceNodeRef = info.getNodeRef();
|
|
if (!info.isDocument())
|
|
{
|
|
throw new CmisConstraintException("Source object is not a document!");
|
|
}
|
|
|
|
// get name and type
|
|
final String name = connector.getNameProperty(properties, info.getName());
|
|
|
|
final TypeDefinitionWrapper type = info.getType();
|
|
connector.checkChildObjectType(parentInfo, type.getTypeId());
|
|
|
|
try
|
|
{
|
|
FileInfo fileInfo = connector.getFileFolderService().copy(
|
|
sourceNodeRef, parentInfo.getNodeRef(), name);
|
|
NodeRef nodeRef = fileInfo.getNodeRef();
|
|
connector.setProperties(nodeRef, type, properties, new String[] {
|
|
PropertyIds.NAME, PropertyIds.OBJECT_TYPE_ID });
|
|
connector.applyPolicies(nodeRef, type, policies);
|
|
connector.applyACL(nodeRef, type, addAces, removeAces);
|
|
connector.applyVersioningState(nodeRef, versioningState);
|
|
|
|
connector.getActivityPoster().postFileFolderAdded(fileInfo);
|
|
|
|
return connector.createObjectId(nodeRef);
|
|
}
|
|
catch (FileNotFoundException e)
|
|
{
|
|
throw new CmisContentAlreadyExistsException("An object with this name already exists!", e);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public String createPolicy(
|
|
String repositoryId, Properties properties, String folderId, List<String> policies,
|
|
Acl addAces, Acl removeAces, ExtensionsData extension)
|
|
{
|
|
checkRepositoryId(repositoryId);
|
|
|
|
// get the parent folder
|
|
getOrCreateFolderInfo(folderId, "Parent Folder");
|
|
|
|
String objectTypeId = connector.getObjectTypeIdProperty(properties);
|
|
connector.getTypeForCreate(objectTypeId, BaseTypeId.CMIS_POLICY);
|
|
|
|
// we should never get here - policies are not creatable!
|
|
throw new CmisRuntimeException("Polcies cannot be created!");
|
|
}
|
|
|
|
@Override
|
|
public String createRelationship(
|
|
String repositoryId, Properties properties, List<String> policies, Acl addAces,
|
|
Acl removeAces, ExtensionsData extension)
|
|
{
|
|
checkRepositoryId(repositoryId);
|
|
|
|
// get type
|
|
String objectTypeId = connector.getObjectTypeIdProperty(properties);
|
|
final TypeDefinitionWrapper type = connector.getTypeForCreate(objectTypeId, BaseTypeId.CMIS_RELATIONSHIP);
|
|
|
|
// get source object
|
|
String sourceId = connector.getSourceIdProperty(properties);
|
|
CMISNodeInfo sourceInfo = getOrCreateNodeInfo(sourceId, "Source");
|
|
|
|
if (!sourceInfo.isVariant(CMISObjectVariant.CURRENT_VERSION) && !sourceInfo.isVariant(CMISObjectVariant.FOLDER))
|
|
{
|
|
throw new CmisInvalidArgumentException("Source is not the latest version of a document or a folder object!");
|
|
}
|
|
|
|
final NodeRef sourceNodeRef = sourceInfo.getNodeRef();
|
|
|
|
// get target object
|
|
String targetId = connector.getTargetIdProperty(properties);
|
|
CMISNodeInfo targetInfo = getOrCreateNodeInfo(targetId, "Target");
|
|
|
|
if (!targetInfo.isVariant(CMISObjectVariant.CURRENT_VERSION) && !targetInfo.isVariant(CMISObjectVariant.FOLDER))
|
|
{
|
|
throw new CmisInvalidArgumentException(
|
|
"Target is not the latest version of a document or a folder object!!");
|
|
}
|
|
|
|
final NodeRef targetNodeRef = targetInfo.getNodeRef();
|
|
|
|
// check policies and ACLs
|
|
if ((policies != null) && (!policies.isEmpty()))
|
|
{
|
|
throw new CmisConstraintException("Relationships are not policy controllable!");
|
|
}
|
|
|
|
if ((addAces != null) && (addAces.getAces() != null) && (!addAces.getAces().isEmpty()))
|
|
{
|
|
throw new CmisConstraintException("Relationships are not ACL controllable!");
|
|
}
|
|
|
|
if ((removeAces != null) && (removeAces.getAces() != null) && (!removeAces.getAces().isEmpty()))
|
|
{
|
|
throw new CmisConstraintException("Relationships are not ACL controllable!");
|
|
}
|
|
|
|
// create relationship
|
|
// ALF-10085 : disable auditing behaviour for this use case
|
|
boolean wasEnabled = connector.disableBehaviour(ContentModel.ASPECT_AUDITABLE, sourceNodeRef); // Lasts for txn
|
|
try
|
|
{
|
|
AssociationRef assocRef = connector.getNodeService().createAssociation(
|
|
sourceNodeRef, targetNodeRef, type.getAlfrescoClass());
|
|
|
|
return CMISConnector.ASSOC_ID_PREFIX + assocRef.getId();
|
|
}
|
|
finally
|
|
{
|
|
if(wasEnabled)
|
|
{
|
|
connector.enableBehaviour(ContentModel.ASPECT_AUDITABLE, sourceNodeRef);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void setContentStream(
|
|
String repositoryId, Holder<String> objectId, Boolean overwriteFlag,
|
|
Holder<String> changeToken, final ContentStream contentStream, ExtensionsData extension)
|
|
{
|
|
checkRepositoryId(repositoryId);
|
|
|
|
CMISNodeInfo info = getOrCreateNodeInfo(objectId.getValue(), "Object");
|
|
|
|
if (!info.isVariant(CMISObjectVariant.CURRENT_VERSION) && !info.isVariant(CMISObjectVariant.PWC))
|
|
{
|
|
throw new CmisStreamNotSupportedException("Content can only be set ondocuments!");
|
|
}
|
|
|
|
final NodeRef nodeRef = info.getNodeRef();
|
|
|
|
if (((DocumentTypeDefinition) info.getType().getTypeDefinition(false)).getContentStreamAllowed() == ContentStreamAllowed.NOTALLOWED)
|
|
{
|
|
throw new CmisStreamNotSupportedException("Document type doesn't allow content!");
|
|
}
|
|
|
|
boolean existed = connector.getContentService().getReader(nodeRef, ContentModel.PROP_CONTENT) != null;
|
|
if (existed && !overwriteFlag)
|
|
{
|
|
throw new CmisContentAlreadyExistsException("Content already exists!");
|
|
}
|
|
|
|
if ((contentStream == null) || (contentStream.getStream() == null))
|
|
{
|
|
throw new CmisInvalidArgumentException("No content!");
|
|
}
|
|
|
|
// copy stream to temp file
|
|
final File tempFile = copyToTempFile(contentStream);
|
|
final Charset encoding = getEncoding(tempFile, contentStream.getMimeType());
|
|
|
|
try
|
|
{
|
|
ContentWriter writer = connector.getFileFolderService().getWriter(nodeRef);
|
|
writer.setMimetype(contentStream.getMimeType());
|
|
writer.setEncoding(encoding.name());
|
|
writer.putContent(tempFile);
|
|
}
|
|
finally
|
|
{
|
|
removeTempFile(tempFile);
|
|
}
|
|
|
|
objectId.setValue(connector.createObjectId(nodeRef));
|
|
|
|
connector.getActivityPoster().postFileFolderUpdated(info.isFolder(), nodeRef);
|
|
}
|
|
|
|
@Override
|
|
public void deleteContentStream(
|
|
String repositoryId, Holder<String> objectId, Holder<String> changeToken,
|
|
ExtensionsData extension)
|
|
{
|
|
checkRepositoryId(repositoryId);
|
|
|
|
CMISNodeInfo info = getOrCreateNodeInfo(objectId.getValue(), "Object");
|
|
|
|
if (!info.isVariant(CMISObjectVariant.CURRENT_VERSION) && !info.isVariant(CMISObjectVariant.PWC))
|
|
{
|
|
throw new CmisStreamNotSupportedException("Content can only be deleted from ondocuments!");
|
|
}
|
|
|
|
final NodeRef nodeRef = info.getNodeRef();
|
|
|
|
if (((DocumentTypeDefinition) info.getType().getTypeDefinition(false)).getContentStreamAllowed() == ContentStreamAllowed.REQUIRED)
|
|
{
|
|
throw new CmisInvalidArgumentException("Document type requires content!");
|
|
}
|
|
|
|
connector.getNodeService().setProperty(nodeRef, ContentModel.PROP_CONTENT, null);
|
|
|
|
connector.getActivityPoster().postFileFolderUpdated(info.isFolder(), nodeRef);
|
|
|
|
objectId.setValue(connector.createObjectId(nodeRef));
|
|
}
|
|
|
|
@Override
|
|
public void moveObject(
|
|
String repositoryId, Holder<String> objectId, String targetFolderId, String sourceFolderId,
|
|
ExtensionsData extension)
|
|
{
|
|
checkRepositoryId(repositoryId);
|
|
|
|
// get object and source and target parent
|
|
CMISNodeInfo info = getOrCreateNodeInfo(objectId.getValue(), "Object");
|
|
|
|
final NodeRef nodeRef = info.getCurrentNodeNodeRef();
|
|
final CMISNodeInfo sourceInfo = getOrCreateFolderInfo(sourceFolderId, "Source Folder");
|
|
final CMISNodeInfo targetInfo = getOrCreateFolderInfo(targetFolderId, "Target Folder");
|
|
|
|
connector.checkChildObjectType(targetInfo, info.getType().getTypeId());
|
|
|
|
ChildAssociationRef primaryParentRef = connector.getNodeService().getPrimaryParent(nodeRef);
|
|
// if this is a primary child node, move it
|
|
if (primaryParentRef.getParentRef().equals(sourceInfo.getNodeRef()))
|
|
{
|
|
connector.getNodeService().moveNode(
|
|
nodeRef, targetInfo.getNodeRef(),
|
|
primaryParentRef.getTypeQName(), primaryParentRef.getQName());
|
|
}
|
|
else
|
|
{
|
|
boolean found = false;
|
|
// otherwise, reparent it
|
|
for (ChildAssociationRef parent : connector.getNodeService().getParentAssocs(
|
|
nodeRef, ContentModel.ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL))
|
|
{
|
|
if (parent.getParentRef().equals(sourceInfo.getNodeRef()))
|
|
{
|
|
connector.getNodeService().removeChildAssociation(parent);
|
|
connector.getNodeService().addChild(
|
|
targetInfo.getNodeRef(), nodeRef,
|
|
ContentModel.ASSOC_CONTAINS, parent.getQName());
|
|
found = true;
|
|
}
|
|
}
|
|
if (!found)
|
|
{
|
|
throw new IllegalArgumentException(
|
|
new CMISInvalidArgumentException(
|
|
"Document is not a child of the source folder that was specified!"));
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void updateProperties(
|
|
String repositoryId, Holder<String> objectId, Holder<String> changeToken,
|
|
final Properties properties, ExtensionsData extension)
|
|
{
|
|
checkRepositoryId(repositoryId);
|
|
|
|
final CMISNodeInfo info = getOrCreateNodeInfo(objectId.getValue(), "Object");
|
|
|
|
if (info.isVariant(CMISObjectVariant.ASSOC))
|
|
{
|
|
throw new CmisInvalidArgumentException("Relationship properties cannot be updated!");
|
|
}
|
|
else
|
|
{
|
|
if (info.isVariant(CMISObjectVariant.VERSION))
|
|
{
|
|
throw new CmisInvalidArgumentException("Document is not the latest version!");
|
|
}
|
|
|
|
final NodeRef nodeRef = info.getNodeRef();
|
|
|
|
connector.setProperties(nodeRef, info.getType(), properties, new String[0]);
|
|
|
|
objectId.setValue(connector.createObjectId(nodeRef));
|
|
|
|
if (context.isObjectInfoRequired())
|
|
{
|
|
getObjectInfo(repositoryId, objectId.getValue(), "*", IncludeRelationships.NONE);
|
|
}
|
|
|
|
connector.getActivityPoster().postFileFolderUpdated(info.isFolder(), nodeRef);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void deleteObject(String repositoryId, String objectId, Boolean allVersions, ExtensionsData extension)
|
|
{
|
|
deleteObjectOrCancelCheckOut(repositoryId, objectId, allVersions, extension);
|
|
}
|
|
|
|
@Override
|
|
public void deleteObjectOrCancelCheckOut(
|
|
String repositoryId, final String objectId, final Boolean allVersions,
|
|
ExtensionsData extension)
|
|
{
|
|
checkRepositoryId(repositoryId);
|
|
|
|
// what kind of object is it?
|
|
final CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object");
|
|
|
|
do
|
|
{
|
|
// handle relationships
|
|
if (info.isVariant(CMISObjectVariant.ASSOC))
|
|
{
|
|
AssociationRef assocRef = info.getAssociationRef();
|
|
connector.getNodeService().removeAssociation(
|
|
assocRef.getSourceRef(),
|
|
assocRef.getTargetRef(),
|
|
assocRef.getTypeQName());
|
|
break; // Reason for do-while
|
|
}
|
|
|
|
NodeRef nodeRef = info.getNodeRef();
|
|
|
|
// handle PWC
|
|
if (info.isVariant(CMISObjectVariant.PWC))
|
|
{
|
|
connector.getCheckOutCheckInService().cancelCheckout(nodeRef);
|
|
break; // Reason for do-while
|
|
}
|
|
|
|
// handle folders
|
|
if (info.isFolder())
|
|
{
|
|
if (connector.getNodeService().getChildAssocs(nodeRef).size() > 0)
|
|
{
|
|
throw new CmisConstraintException(
|
|
"Could not delete folder with at least one child!");
|
|
}
|
|
|
|
connector.deleteNode(nodeRef, false);
|
|
break; // Reason for do-while
|
|
}
|
|
|
|
// handle versions
|
|
if (allVersions)
|
|
{
|
|
NodeRef workingCopy = connector.getCheckOutCheckInService().getWorkingCopy(nodeRef);
|
|
if (workingCopy != null)
|
|
{
|
|
connector.getCheckOutCheckInService().cancelCheckout(workingCopy);
|
|
}
|
|
}
|
|
else if (info.isVariant(CMISObjectVariant.VERSION))
|
|
{
|
|
Version version = ((CMISNodeInfoImpl) info).getVersion();
|
|
connector.getVersionService().deleteVersion(nodeRef, version);
|
|
break; // Reason for do-while
|
|
}
|
|
|
|
if (info.isVariant(CMISObjectVariant.VERSION))
|
|
{
|
|
nodeRef = info.getCurrentNodeNodeRef();
|
|
}
|
|
|
|
// attempt to delete the node
|
|
if (allVersions)
|
|
{
|
|
connector.deleteNode(nodeRef, true);
|
|
}
|
|
else
|
|
{
|
|
CMISNodeInfoImpl infoImpl = ((CMISNodeInfoImpl) info);
|
|
Version version = infoImpl.getVersion();
|
|
|
|
if (infoImpl.getVersionHistory().getPredecessor(version) == null)
|
|
{
|
|
connector.deleteNode(nodeRef, true);
|
|
}
|
|
else
|
|
{
|
|
connector.getVersionService().deleteVersion(nodeRef, version);
|
|
}
|
|
}
|
|
}
|
|
while (false); // Dodgey, but avoided having to play with too much code during refactor
|
|
}
|
|
|
|
@Override
|
|
public FailedToDeleteData deleteTree(
|
|
String repositoryId, String folderId, Boolean allVersions,
|
|
UnfileObject unfileObjects, final Boolean continueOnFailure, ExtensionsData extension)
|
|
{
|
|
checkRepositoryId(repositoryId);
|
|
|
|
if (!allVersions)
|
|
{
|
|
throw new CmisInvalidArgumentException("Only allVersions=true supported!");
|
|
}
|
|
|
|
if (unfileObjects == UnfileObject.UNFILE)
|
|
{
|
|
throw new CmisInvalidArgumentException("Unfiling not supported!");
|
|
}
|
|
|
|
final NodeRef folderNodeRef = getOrCreateFolderInfo(folderId, "Folder").getNodeRef();
|
|
final FailedToDeleteDataImpl result = new FailedToDeleteDataImpl();
|
|
|
|
result.setIds(deleteBranch(folderNodeRef, continueOnFailure));
|
|
return result;
|
|
}
|
|
|
|
private List<String> deleteBranch(NodeRef nodeRef, boolean continueOnFailure)
|
|
{
|
|
List<String> result = new ArrayList<String>();
|
|
|
|
try
|
|
{
|
|
// remove children
|
|
List<ChildAssociationRef> childrenList = connector.getNodeService().getChildAssocs(nodeRef);
|
|
if (childrenList != null)
|
|
{
|
|
for (ChildAssociationRef child : childrenList)
|
|
{
|
|
List<String> ftod = deleteBranch(child.getChildRef(), continueOnFailure);
|
|
if (!ftod.isEmpty())
|
|
{
|
|
result.addAll(ftod);
|
|
if (!continueOnFailure)
|
|
{
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// remove not primary parent associations
|
|
// TODO: This can be removed once we fix node archival
|
|
List<ChildAssociationRef> childAssociations = connector.getNodeService().getParentAssocs(nodeRef);
|
|
if (childAssociations != null)
|
|
{
|
|
for (ChildAssociationRef childAssoc : childAssociations)
|
|
{
|
|
if (!childAssoc.isPrimary())
|
|
{
|
|
connector.getNodeService().removeChildAssociation(childAssoc);
|
|
}
|
|
}
|
|
}
|
|
|
|
// attempt to delete the node
|
|
connector.deleteNode(nodeRef, false);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
result.add(nodeRef.toString());
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public ObjectData getObject(
|
|
String repositoryId, String objectId, String filter, Boolean includeAllowableActions,
|
|
IncludeRelationships includeRelationships, String renditionFilter, Boolean includePolicyIds,
|
|
Boolean includeAcl, ExtensionsData extension)
|
|
{
|
|
checkRepositoryId(repositoryId);
|
|
|
|
// what kind of object is it?
|
|
CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object");
|
|
|
|
// create a CMIS object
|
|
ObjectData object = connector.createCMISObject(
|
|
info, filter, includeAllowableActions, includeRelationships,
|
|
renditionFilter, includePolicyIds, includeAcl);
|
|
|
|
if (context.isObjectInfoRequired())
|
|
{
|
|
getObjectInfo(repositoryId, info.getObjectId(), includeRelationships);
|
|
}
|
|
|
|
return object;
|
|
}
|
|
|
|
@Override
|
|
public ObjectData getObjectByPath(
|
|
String repositoryId, String path, String filter, Boolean includeAllowableActions,
|
|
IncludeRelationships includeRelationships, String renditionFilter, Boolean includePolicyIds,
|
|
Boolean includeAcl, ExtensionsData extension)
|
|
{
|
|
checkRepositoryId(repositoryId);
|
|
|
|
// start at the root node
|
|
NodeRef rootNodeRef = connector.getRootNodeRef();
|
|
|
|
if (path.equals("/"))
|
|
{
|
|
return connector.createCMISObject(createNodeInfo(rootNodeRef), filter, includeAllowableActions,
|
|
includeRelationships, renditionFilter, includePolicyIds, includeAcl);
|
|
}
|
|
else
|
|
{
|
|
try
|
|
{
|
|
// resolve path and get the node ref
|
|
FileInfo fileInfo = connector.getFileFolderService().resolveNamePath(
|
|
rootNodeRef,
|
|
Arrays.asList(path.substring(1).split("/")));
|
|
|
|
CMISNodeInfo info = createNodeInfo(fileInfo.getNodeRef());
|
|
|
|
ObjectData object = connector.createCMISObject(
|
|
info, fileInfo, filter, includeAllowableActions,
|
|
includeRelationships, renditionFilter, includePolicyIds, includeAcl);
|
|
|
|
if (context.isObjectInfoRequired())
|
|
{
|
|
getObjectInfo(repositoryId, info.getObjectId(), includeRelationships);
|
|
}
|
|
|
|
return object;
|
|
}
|
|
catch (FileNotFoundException e)
|
|
{
|
|
throw new CmisObjectNotFoundException("Object not found: " + path);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public Properties getProperties(String repositoryId, String objectId, String filter, ExtensionsData extension)
|
|
{
|
|
checkRepositoryId(repositoryId);
|
|
|
|
// what kind of object is it?
|
|
CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object");
|
|
|
|
if (context.isObjectInfoRequired())
|
|
{
|
|
getObjectInfo(repositoryId, info.getObjectId(), IncludeRelationships.NONE);
|
|
}
|
|
|
|
if (info.isVariant(CMISObjectVariant.ASSOC))
|
|
{
|
|
return connector.getAssocProperties(info, filter);
|
|
}
|
|
else
|
|
{
|
|
return connector.getNodeProperties(info, filter);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public AllowableActions getAllowableActions(String repositoryId, String objectId, ExtensionsData extension)
|
|
{
|
|
checkRepositoryId(repositoryId);
|
|
|
|
// what kind of object is it?
|
|
CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object");
|
|
|
|
return connector.getAllowableActions(info);
|
|
}
|
|
|
|
@Override
|
|
public ContentStream getContentStream(
|
|
String repositoryId, String objectId, String streamId, BigInteger offset,
|
|
BigInteger length, ExtensionsData extension)
|
|
{
|
|
checkRepositoryId(repositoryId);
|
|
|
|
// what kind of object is it?
|
|
CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object");
|
|
|
|
// relationships cannot have content
|
|
if (info.isVariant(CMISObjectVariant.ASSOC))
|
|
{
|
|
throw new CmisInvalidArgumentException("Object is a relationship and cannot have content!");
|
|
}
|
|
|
|
// now get it
|
|
return connector.getContentStream(info, streamId, offset, length);
|
|
}
|
|
|
|
@Override
|
|
public List<RenditionData> getRenditions(String repositoryId, String objectId, String renditionFilter,
|
|
BigInteger maxItems, BigInteger skipCount, ExtensionsData extension)
|
|
{
|
|
checkRepositoryId(repositoryId);
|
|
|
|
// what kind of object is it?
|
|
CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object");
|
|
|
|
if (info.isVariant(CMISObjectVariant.ASSOC))
|
|
{
|
|
return Collections.emptyList();
|
|
}
|
|
else
|
|
{
|
|
return connector.getRenditions(info.getNodeRef(), renditionFilter, maxItems, skipCount);
|
|
}
|
|
}
|
|
|
|
// --- versioning service ---
|
|
|
|
@Override
|
|
public void checkOut(
|
|
String repositoryId, final Holder<String> objectId, ExtensionsData extension,
|
|
final Holder<Boolean> contentCopied)
|
|
{
|
|
checkRepositoryId(repositoryId);
|
|
|
|
CMISNodeInfo info = getOrCreateNodeInfo(objectId.getValue(), "Object");
|
|
|
|
// Check for current version
|
|
if (info.isVariant(CMISObjectVariant.CURRENT_VERSION))
|
|
{
|
|
// Good
|
|
}
|
|
else if (info.isVariant(CMISObjectVariant.VERSION))
|
|
{
|
|
throw new CmisInvalidArgumentException("Can't check out an old version of a document");
|
|
}
|
|
else {
|
|
throw new CmisInvalidArgumentException("Only documents can be checked out! Object was a " + info.getObjectVariant().toString());
|
|
}
|
|
|
|
// get object
|
|
final NodeRef nodeRef = info.getNodeRef();
|
|
|
|
if (!((DocumentTypeDefinition) info.getType().getTypeDefinition(false)).isVersionable())
|
|
{
|
|
throw new CmisConstraintException("Document is not versionable!");
|
|
}
|
|
|
|
// check out
|
|
NodeRef pwcNodeRef = connector.getCheckOutCheckInService().checkout(nodeRef);
|
|
CMISNodeInfo pwcNodeInfo = createNodeInfo(pwcNodeRef);
|
|
objectId.setValue(pwcNodeInfo.getObjectId());
|
|
|
|
if (contentCopied != null)
|
|
{
|
|
contentCopied.setValue(connector.getFileFolderService().getReader(pwcNodeRef) != null);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void cancelCheckOut(String repositoryId, String objectId, ExtensionsData extension)
|
|
{
|
|
checkRepositoryId(repositoryId);
|
|
|
|
CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object");
|
|
|
|
// only accept a PWC
|
|
if (!info.isVariant(CMISObjectVariant.PWC))
|
|
{
|
|
throw new CmisVersioningException("Object is not a PWC!");
|
|
}
|
|
|
|
// get object
|
|
final NodeRef nodeRef = info.getNodeRef();
|
|
|
|
// cancel check out
|
|
connector.getCheckOutCheckInService().cancelCheckout(nodeRef);
|
|
}
|
|
|
|
@Override
|
|
public void checkIn(
|
|
String repositoryId, final Holder<String> objectId, final Boolean major,
|
|
final Properties properties, final ContentStream contentStream, final String checkinComment,
|
|
final List<String> policies, final Acl addAces, final Acl removeAces, ExtensionsData extension)
|
|
{
|
|
checkRepositoryId(repositoryId);
|
|
|
|
CMISNodeInfo info = getOrCreateNodeInfo(objectId.getValue(), "Object");
|
|
|
|
// only accept a PWC
|
|
if (!info.isVariant(CMISObjectVariant.PWC))
|
|
{
|
|
throw new CmisVersioningException("Object is not a PWC!");
|
|
}
|
|
|
|
// get object
|
|
final NodeRef nodeRef = info.getNodeRef();
|
|
final TypeDefinitionWrapper type = info.getType();
|
|
|
|
// copy stream to temp file
|
|
final File tempFile = copyToTempFile(contentStream);
|
|
final Charset encoding = (tempFile == null ? null : getEncoding(tempFile, contentStream.getMimeType()));
|
|
|
|
// check in
|
|
// update PWC
|
|
connector.setProperties(nodeRef, type, properties,
|
|
new String[] { PropertyIds.OBJECT_TYPE_ID });
|
|
connector.applyPolicies(nodeRef, type, policies);
|
|
connector.applyACL(nodeRef, type, addAces, removeAces);
|
|
|
|
// handle content
|
|
if (contentStream != null)
|
|
{
|
|
// write content
|
|
ContentWriter writer = connector.getFileFolderService().getWriter(nodeRef);
|
|
writer.setMimetype(contentStream.getMimeType());
|
|
writer.setEncoding(encoding.name());
|
|
writer.putContent(tempFile);
|
|
}
|
|
|
|
// check aspect
|
|
if (connector.getNodeService().hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE) == false)
|
|
{
|
|
Map<QName, Serializable> props = new HashMap<QName, Serializable>();
|
|
props.put(ContentModel.PROP_INITIAL_VERSION, false);
|
|
props.put(ContentModel.PROP_AUTO_VERSION, false);
|
|
connector.getNodeService().addAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE, props);
|
|
}
|
|
|
|
// create version properties
|
|
Map<String, Serializable> versionProperties = new HashMap<String, Serializable>(5);
|
|
versionProperties.put(VersionModel.PROP_VERSION_TYPE, major ? VersionType.MAJOR
|
|
: VersionType.MINOR);
|
|
if (checkinComment != null)
|
|
{
|
|
versionProperties.put(VersionModel.PROP_DESCRIPTION, checkinComment);
|
|
}
|
|
|
|
// check in
|
|
NodeRef newNodeRef = connector.getCheckOutCheckInService().checkin(nodeRef, versionProperties);
|
|
|
|
objectId.setValue(connector.createObjectId(newNodeRef));
|
|
|
|
removeTempFile(tempFile);
|
|
}
|
|
|
|
@Override
|
|
public List<ObjectData> getAllVersions(
|
|
String repositoryId, String objectId, String versionSeriesId, String filter,
|
|
Boolean includeAllowableActions, ExtensionsData extension)
|
|
{
|
|
checkRepositoryId(repositoryId);
|
|
|
|
if (versionSeriesId == null && objectId != null)
|
|
{
|
|
// it's a browser binding call
|
|
versionSeriesId = connector.getCurrentVersionId(objectId);
|
|
}
|
|
|
|
if (versionSeriesId == null)
|
|
{
|
|
throw new CmisInvalidArgumentException("Object Id or Object Series Id must be set!");
|
|
}
|
|
|
|
List<ObjectData> result = new ArrayList<ObjectData>();
|
|
|
|
// what kind of object is it?
|
|
CMISNodeInfo info = getOrCreateNodeInfo(versionSeriesId, "Version Series");
|
|
|
|
if (!info.isVariant(CMISObjectVariant.CURRENT_VERSION))
|
|
{
|
|
// the version series id is the id of current version, which is a
|
|
// document
|
|
throw new CmisInvalidArgumentException("Version Series does not exist!");
|
|
}
|
|
|
|
// get current version and it's history
|
|
NodeRef nodeRef = info.getNodeRef();
|
|
VersionHistory versionHistory = ((CMISNodeInfoImpl) info).getVersionHistory();
|
|
|
|
if (versionHistory == null)
|
|
{
|
|
// add current version
|
|
result.add(connector.createCMISObject(info, filter, includeAllowableActions, IncludeRelationships.NONE,
|
|
CMISConnector.RENDITION_NONE, false, false));
|
|
|
|
if (context.isObjectInfoRequired())
|
|
{
|
|
getObjectInfo(repositoryId, info.getObjectId(), IncludeRelationships.NONE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (info.hasPWC())
|
|
{
|
|
CMISNodeInfo pwcInfo = createNodeInfo(connector.getCheckOutCheckInService().getWorkingCopy(nodeRef));
|
|
|
|
result.add(
|
|
connector.createCMISObject(
|
|
pwcInfo, filter, includeAllowableActions,
|
|
IncludeRelationships.NONE, CMISConnector.RENDITION_NONE, false, false));
|
|
|
|
if (context.isObjectInfoRequired())
|
|
{
|
|
getObjectInfo(repositoryId, pwcInfo.getObjectId(), IncludeRelationships.NONE);
|
|
}
|
|
}
|
|
|
|
// convert the version history
|
|
for (Version version : versionHistory.getAllVersions())
|
|
{
|
|
CMISNodeInfo versionInfo = createNodeInfo(version.getFrozenStateNodeRef(), versionHistory);
|
|
|
|
result.add(
|
|
connector.createCMISObject(
|
|
versionInfo, filter, includeAllowableActions,
|
|
IncludeRelationships.NONE, CMISConnector.RENDITION_NONE, false, false));
|
|
|
|
if (context.isObjectInfoRequired())
|
|
{
|
|
getObjectInfo(repositoryId, versionInfo.getObjectId(), IncludeRelationships.NONE);
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public ObjectData getObjectOfLatestVersion(
|
|
String repositoryId, String objectId, String versionSeriesId,
|
|
Boolean major, String filter, Boolean includeAllowableActions, IncludeRelationships includeRelationships,
|
|
String renditionFilter, Boolean includePolicyIds, Boolean includeAcl, ExtensionsData extension)
|
|
{
|
|
checkRepositoryId(repositoryId);
|
|
|
|
if (objectId != null)
|
|
{
|
|
// it's an AtomPub call
|
|
versionSeriesId = connector.getCurrentVersionId(objectId);
|
|
}
|
|
|
|
// what kind of object is it?
|
|
CMISNodeInfo info = getOrCreateNodeInfo(versionSeriesId, "Version Series");
|
|
CMISNodeInfo versionInfo = createNodeInfo(((CMISNodeInfoImpl) info).getLatestVersionNodeRef(major));
|
|
|
|
ObjectData object = connector.createCMISObject(
|
|
versionInfo, filter, includeAllowableActions,
|
|
includeRelationships, renditionFilter, includePolicyIds, includeAcl);
|
|
|
|
if (context.isObjectInfoRequired())
|
|
{
|
|
getObjectInfo(repositoryId, info.getObjectId(), includeRelationships);
|
|
}
|
|
|
|
return object;
|
|
}
|
|
|
|
@Override
|
|
public Properties getPropertiesOfLatestVersion(
|
|
String repositoryId, String objectId, String versionSeriesId,
|
|
Boolean major, String filter, ExtensionsData extension)
|
|
{
|
|
checkRepositoryId(repositoryId);
|
|
|
|
if (objectId != null)
|
|
{
|
|
// it's an AtomPub call
|
|
versionSeriesId = connector.getCurrentVersionId(objectId);
|
|
}
|
|
|
|
// what kind of object is it?
|
|
CMISNodeInfo info = getOrCreateNodeInfo(versionSeriesId, "Version Series");
|
|
|
|
if (info.isVariant(CMISObjectVariant.ASSOC))
|
|
{
|
|
return connector.getAssocProperties(info, filter);
|
|
}
|
|
else
|
|
{
|
|
CMISNodeInfo versionInfo = createNodeInfo(((CMISNodeInfoImpl) info).getLatestVersionNodeRef(major));
|
|
addNodeInfo(versionInfo);
|
|
return connector.getNodeProperties(versionInfo, filter);
|
|
}
|
|
}
|
|
|
|
// --- multifiling service ---
|
|
|
|
@Override
|
|
public void addObjectToFolder(
|
|
String repositoryId, String objectId, String folderId, Boolean allVersions,
|
|
ExtensionsData extension)
|
|
{
|
|
checkRepositoryId(repositoryId);
|
|
|
|
if (!allVersions)
|
|
{
|
|
throw new CmisInvalidArgumentException("Only allVersions=true supported!");
|
|
}
|
|
|
|
// get node ref
|
|
CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object");
|
|
|
|
if (!info.isDocument())
|
|
{
|
|
throw new CmisInvalidArgumentException("Object is not a document!");
|
|
}
|
|
|
|
final NodeRef nodeRef = info.getNodeRef();
|
|
|
|
// get the folder node ref
|
|
final CMISNodeInfo folderInfo = getOrCreateFolderInfo(folderId, "Folder");
|
|
|
|
connector.checkChildObjectType(folderInfo, info.getType().getTypeId());
|
|
|
|
final QName name = QName.createQName(
|
|
NamespaceService.CONTENT_MODEL_1_0_URI,
|
|
QName.createValidLocalName((String) connector.getNodeService().getProperty(nodeRef,
|
|
ContentModel.PROP_NAME)));
|
|
|
|
connector.getNodeService().addChild(
|
|
folderInfo.getNodeRef(), nodeRef, ContentModel.ASSOC_CONTAINS, name);
|
|
}
|
|
|
|
@Override
|
|
public void removeObjectFromFolder(String repositoryId, String objectId, String folderId, ExtensionsData extension)
|
|
{
|
|
checkRepositoryId(repositoryId);
|
|
|
|
// get node ref
|
|
CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object");
|
|
|
|
if (!info.isDocument())
|
|
{
|
|
throw new CmisInvalidArgumentException("Object is not a document!");
|
|
}
|
|
|
|
final NodeRef nodeRef = info.getNodeRef();
|
|
|
|
// get the folder node ref
|
|
final NodeRef folderNodeRef = getOrCreateFolderInfo(folderId, "Folder").getNodeRef();
|
|
|
|
// check primary parent
|
|
if (connector.getNodeService().getPrimaryParent(nodeRef).getParentRef().equals(folderNodeRef))
|
|
{
|
|
throw new CmisConstraintException(
|
|
"Unfiling from primary parent folder is not supported! Use deleteObject() instead.");
|
|
}
|
|
|
|
connector.getNodeService().removeChild(folderNodeRef, nodeRef);
|
|
}
|
|
|
|
// --- discovery service ---
|
|
|
|
@Override
|
|
public ObjectList getContentChanges(
|
|
String repositoryId, Holder<String> changeLogToken, Boolean includeProperties,
|
|
String filter, Boolean includePolicyIds, Boolean includeAcl, BigInteger maxItems, ExtensionsData extension)
|
|
{
|
|
checkRepositoryId(repositoryId);
|
|
|
|
return connector.getContentChanges(changeLogToken, maxItems);
|
|
}
|
|
|
|
@Override
|
|
public ObjectList query(String repositoryId, String statement, Boolean searchAllVersions,
|
|
Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter,
|
|
BigInteger maxItems, BigInteger skipCount, ExtensionsData extension)
|
|
{
|
|
checkRepositoryId(repositoryId);
|
|
|
|
if (searchAllVersions.booleanValue())
|
|
{
|
|
throw new CmisInvalidArgumentException("Search all version is not supported!");
|
|
}
|
|
|
|
return connector.query(
|
|
statement, includeAllowableActions, includeRelationships, renditionFilter,
|
|
maxItems, skipCount);
|
|
}
|
|
|
|
// --- relationship service ---
|
|
|
|
@Override
|
|
public ObjectList getObjectRelationships(
|
|
String repositoryId, String objectId, Boolean includeSubRelationshipTypes,
|
|
RelationshipDirection relationshipDirection, String typeId, String filter, Boolean includeAllowableActions,
|
|
BigInteger maxItems, BigInteger skipCount, ExtensionsData extension)
|
|
{
|
|
checkRepositoryId(repositoryId);
|
|
|
|
// what kind of object is it?
|
|
CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object");
|
|
|
|
if (info.isVariant(CMISObjectVariant.ASSOC))
|
|
{
|
|
throw new CmisInvalidArgumentException("Object is a relationship!");
|
|
}
|
|
|
|
if (info.isVariant(CMISObjectVariant.VERSION))
|
|
{
|
|
throw new CmisInvalidArgumentException("Object is a document version!");
|
|
}
|
|
|
|
// check if the relationship base type is requested
|
|
if (BaseTypeId.CMIS_RELATIONSHIP.value().equals(typeId))
|
|
{
|
|
boolean isrt = (includeSubRelationshipTypes == null ? false : includeSubRelationshipTypes.booleanValue());
|
|
if (isrt)
|
|
{
|
|
// all relationships are a direct subtype of the base type in
|
|
// Alfresco -> remove filter
|
|
typeId = null;
|
|
}
|
|
else
|
|
{
|
|
// there are no relationships of the base type in Alfresco ->
|
|
// return empty list
|
|
ObjectListImpl result = new ObjectListImpl();
|
|
result.setHasMoreItems(false);
|
|
result.setNumItems(BigInteger.ZERO);
|
|
result.setObjects(new ArrayList<ObjectData>());
|
|
return result;
|
|
}
|
|
}
|
|
|
|
return connector.getObjectRelationships(
|
|
info.getNodeRef(), relationshipDirection, typeId, filter, includeAllowableActions,
|
|
maxItems, skipCount);
|
|
}
|
|
|
|
// --- policy service ---
|
|
|
|
@Override
|
|
public void applyPolicy(String repositoryId, String policyId, String objectId, ExtensionsData extension)
|
|
{
|
|
checkRepositoryId(repositoryId);
|
|
|
|
// what kind of object is it?
|
|
CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object");
|
|
|
|
TypeDefinitionWrapper type = info.getType();
|
|
if (type == null)
|
|
{
|
|
throw new CmisObjectNotFoundException("No corresponding type found! Not a CMIS object?");
|
|
}
|
|
|
|
connector.applyPolicies(info.getNodeRef(), type, Collections.singletonList(policyId));
|
|
}
|
|
|
|
@Override
|
|
public void removePolicy(String repositoryId, String policyId, String objectId, ExtensionsData extension)
|
|
{
|
|
checkRepositoryId(repositoryId);
|
|
|
|
// what kind of object is it?
|
|
CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object");
|
|
|
|
TypeDefinitionWrapper type = info.getType();
|
|
if (type == null)
|
|
{
|
|
throw new CmisObjectNotFoundException("No corresponding type found! Not a CMIS object?");
|
|
}
|
|
|
|
throw new CmisConstraintException("Object is not policy controllable!");
|
|
}
|
|
|
|
@Override
|
|
public List<ObjectData> getAppliedPolicies(
|
|
String repositoryId, String objectId, String filter, ExtensionsData extension)
|
|
{
|
|
checkRepositoryId(repositoryId);
|
|
|
|
// what kind of object is it?
|
|
getOrCreateNodeInfo(objectId, "Object");
|
|
|
|
// policies are not supported -> return empty list
|
|
return Collections.emptyList();
|
|
}
|
|
|
|
// --- ACL service ---
|
|
|
|
@Override
|
|
public Acl applyAcl(
|
|
String repositoryId, String objectId, final Acl addAces, final Acl removeAces,
|
|
AclPropagation aclPropagation, ExtensionsData extension)
|
|
{
|
|
checkRepositoryId(repositoryId);
|
|
|
|
// We are spec compliant if we just let it through and the tck will not fail
|
|
|
|
CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object");
|
|
|
|
// relationships don't have ACLs
|
|
if (info.isVariant(CMISObjectVariant.ASSOC))
|
|
{
|
|
throw new CmisConstraintException("Relationships are not ACL controllable!");
|
|
}
|
|
|
|
final NodeRef nodeRef = info.getCurrentNodeNodeRef();
|
|
final TypeDefinitionWrapper type = info.getType();
|
|
|
|
connector.applyACL(nodeRef, type, addAces, removeAces);
|
|
|
|
return connector.getACL(nodeRef, false);
|
|
}
|
|
|
|
@Override
|
|
public Acl applyAcl(String repositoryId, String objectId, final Acl aces, AclPropagation aclPropagation)
|
|
{
|
|
checkRepositoryId(repositoryId);
|
|
|
|
// We are spec compliant if we just let it through and the tck will not fail
|
|
|
|
CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object");
|
|
|
|
// relationships don't have ACLs
|
|
if (info.isVariant(CMISObjectVariant.ASSOC))
|
|
{
|
|
throw new CmisConstraintException("Relationships are not ACL controllable!");
|
|
}
|
|
|
|
final NodeRef nodeRef = info.getCurrentNodeNodeRef();
|
|
final TypeDefinitionWrapper type = info.getType();
|
|
|
|
connector.applyACL(nodeRef, type, aces);
|
|
|
|
return connector.getACL(nodeRef, false);
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
@Override
|
|
public Acl getAcl(String repositoryId, String objectId, Boolean onlyBasicPermissions, ExtensionsData extension)
|
|
{
|
|
checkRepositoryId(repositoryId);
|
|
|
|
CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object");
|
|
|
|
// relationships don't have ACLs
|
|
if (info.isVariant(CMISObjectVariant.ASSOC))
|
|
{
|
|
return new AccessControlListImpl(Collections.EMPTY_LIST);
|
|
}
|
|
|
|
// get the ACL
|
|
return connector.getACL(info.getCurrentNodeNodeRef(), onlyBasicPermissions);
|
|
}
|
|
|
|
// --------------------------------------------------------
|
|
|
|
/**
|
|
* Collects the {@link ObjectInfo} about an object.
|
|
*
|
|
* (Provided by OpenCMIS, but optimized for Alfresco.)
|
|
*/
|
|
@Override
|
|
public ObjectInfo getObjectInfo(String repositoryId, String objectId)
|
|
{
|
|
return getObjectInfo(repositoryId, objectId, null, IncludeRelationships.BOTH);
|
|
}
|
|
|
|
protected ObjectInfo getObjectInfo(String repositoryId, String objectId, IncludeRelationships includeRelationships)
|
|
{
|
|
return getObjectInfo(repositoryId, objectId, null, includeRelationships);
|
|
}
|
|
|
|
protected ObjectInfo getObjectInfo(
|
|
String repositoryId, String objectId, String filter,
|
|
IncludeRelationships includeRelationships)
|
|
{
|
|
ObjectInfo info = objectInfoMap.get(objectId);
|
|
if (info == null)
|
|
{
|
|
CMISNodeInfo nodeInfo = getOrCreateNodeInfo(objectId);
|
|
|
|
if (nodeInfo.getObjectVariant() == CMISObjectVariant.INVALID_ID
|
|
|| nodeInfo.getObjectVariant() == CMISObjectVariant.NOT_EXISTING
|
|
|| nodeInfo.getObjectVariant() == CMISObjectVariant.NOT_A_CMIS_OBJECT
|
|
|| nodeInfo.getObjectVariant() == CMISObjectVariant.PERMISSION_DENIED)
|
|
{
|
|
info = null;
|
|
} else
|
|
{
|
|
// object info has not been found -> create one
|
|
try
|
|
{
|
|
if (filter == null)
|
|
{
|
|
filter = MIN_FILTER;
|
|
}
|
|
else if (!filter.equals("*"))
|
|
{
|
|
filter = filter + "," + MIN_FILTER;
|
|
}
|
|
|
|
// get the object and its info
|
|
ObjectData object = connector.createCMISObject(
|
|
nodeInfo, filter, false, includeRelationships, null, false, false);
|
|
|
|
info = getObjectInfoIntern(repositoryId, object);
|
|
|
|
// add object info
|
|
objectInfoMap.put(objectId, info);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
e.printStackTrace();
|
|
info = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
return info;
|
|
}
|
|
|
|
/**
|
|
* Collects the {@link ObjectInfo} about an object.
|
|
*
|
|
* (Provided by OpenCMIS, but optimized for Alfresco.)
|
|
*/
|
|
@Override
|
|
protected ObjectInfo getObjectInfoIntern(String repositoryId, ObjectData object)
|
|
{
|
|
// if the object has no properties, stop here
|
|
if (object.getProperties() == null || object.getProperties().getProperties() == null)
|
|
{
|
|
throw new CmisRuntimeException("No properties!");
|
|
}
|
|
|
|
CMISNodeInfo ni = getOrCreateNodeInfo(object.getId());
|
|
|
|
ObjectInfoImpl info = new ObjectInfoImpl();
|
|
|
|
// general properties
|
|
info.setObject(object);
|
|
info.setId(object.getId());
|
|
info.setName(ni.getName());
|
|
info.setCreatedBy(getStringProperty(object, PropertyIds.CREATED_BY));
|
|
info.setCreationDate(getDateTimeProperty(object, PropertyIds.CREATION_DATE));
|
|
info.setLastModificationDate(getDateTimeProperty(object, PropertyIds.LAST_MODIFICATION_DATE));
|
|
info.setTypeId(getIdProperty(object, PropertyIds.OBJECT_TYPE_ID));
|
|
info.setBaseType(object.getBaseTypeId());
|
|
|
|
if (ni.isRelationship())
|
|
{
|
|
// versioning
|
|
info.setWorkingCopyId(null);
|
|
info.setWorkingCopyOriginalId(null);
|
|
|
|
info.setVersionSeriesId(null);
|
|
info.setIsCurrentVersion(true);
|
|
info.setWorkingCopyId(null);
|
|
info.setWorkingCopyOriginalId(null);
|
|
|
|
// content
|
|
info.setHasContent(false);
|
|
info.setContentType(null);
|
|
info.setFileName(null);
|
|
|
|
// parent
|
|
info.setHasParent(false);
|
|
|
|
// policies and relationships
|
|
info.setSupportsRelationships(false);
|
|
info.setSupportsPolicies(false);
|
|
|
|
// renditions
|
|
info.setRenditionInfos(null);
|
|
|
|
// relationships
|
|
info.setRelationshipSourceIds(null);
|
|
info.setRelationshipTargetIds(null);
|
|
|
|
// global settings
|
|
info.setHasAcl(false);
|
|
info.setSupportsDescendants(false);
|
|
info.setSupportsFolderTree(false);
|
|
}
|
|
else if (ni.isFolder())
|
|
{
|
|
// versioning
|
|
info.setWorkingCopyId(null);
|
|
info.setWorkingCopyOriginalId(null);
|
|
|
|
info.setVersionSeriesId(null);
|
|
info.setIsCurrentVersion(true);
|
|
info.setWorkingCopyId(null);
|
|
info.setWorkingCopyOriginalId(null);
|
|
|
|
// content
|
|
info.setHasContent(false);
|
|
info.setContentType(null);
|
|
info.setFileName(null);
|
|
|
|
// parent
|
|
info.setHasParent(!ni.isRootFolder());
|
|
|
|
// policies and relationships
|
|
info.setSupportsRelationships(true);
|
|
info.setSupportsPolicies(true);
|
|
|
|
// renditions
|
|
info.setRenditionInfos(null);
|
|
|
|
// relationships
|
|
setRelaionshipsToObjectInfo(object, info);
|
|
|
|
// global settings
|
|
info.setHasAcl(true);
|
|
info.setSupportsDescendants(true);
|
|
info.setSupportsFolderTree(true);
|
|
}
|
|
else if (ni.isDocument())
|
|
{
|
|
// versioning
|
|
info.setWorkingCopyId(null);
|
|
info.setWorkingCopyOriginalId(null);
|
|
|
|
info.setVersionSeriesId(ni.getCurrentNodeId());
|
|
|
|
if (ni.isPWC())
|
|
{
|
|
info.setIsCurrentVersion(false);
|
|
info.setWorkingCopyId(ni.getObjectId());
|
|
info.setWorkingCopyOriginalId(ni.getCurrentObjectId());
|
|
}
|
|
else
|
|
{
|
|
info.setIsCurrentVersion(ni.isCurrentVersion());
|
|
|
|
if (ni.hasPWC())
|
|
{
|
|
info.setWorkingCopyId(ni.getCurrentNodeId() + CMISConnector.ID_SEPERATOR
|
|
+ CMISConnector.PWC_VERSION_LABEL);
|
|
info.setWorkingCopyOriginalId(ni.getCurrentObjectId());
|
|
} else
|
|
{
|
|
info.setWorkingCopyId(null);
|
|
info.setWorkingCopyOriginalId(null);
|
|
}
|
|
}
|
|
|
|
// content
|
|
String fileName = getStringProperty(object, PropertyIds.CONTENT_STREAM_FILE_NAME);
|
|
String mimeType = getStringProperty(object, PropertyIds.CONTENT_STREAM_MIME_TYPE);
|
|
String streamId = getIdProperty(object, PropertyIds.CONTENT_STREAM_ID);
|
|
BigInteger length = getIntegerProperty(object, PropertyIds.CONTENT_STREAM_LENGTH);
|
|
boolean hasContent = fileName != null || mimeType != null || streamId != null || length != null;
|
|
if (hasContent)
|
|
{
|
|
info.setHasContent(hasContent);
|
|
info.setContentType(mimeType);
|
|
info.setFileName(fileName);
|
|
}
|
|
else
|
|
{
|
|
info.setHasContent(false);
|
|
info.setContentType(null);
|
|
info.setFileName(null);
|
|
}
|
|
|
|
// parent
|
|
info.setHasParent(ni.isCurrentVersion() || ni.isPWC());
|
|
|
|
// policies and relationships
|
|
info.setSupportsRelationships(true);
|
|
info.setSupportsPolicies(true);
|
|
|
|
// renditions
|
|
info.setRenditionInfos(null);
|
|
List<RenditionData> renditions = object.getRenditions();
|
|
if (renditions != null && renditions.size() > 0)
|
|
{
|
|
List<RenditionInfo> renditionInfos = new ArrayList<RenditionInfo>();
|
|
for (RenditionData rendition : renditions)
|
|
{
|
|
RenditionInfoImpl renditionInfo = new RenditionInfoImpl();
|
|
renditionInfo.setId(rendition.getStreamId());
|
|
renditionInfo.setKind(rendition.getKind());
|
|
renditionInfo.setContentType(rendition.getMimeType());
|
|
renditionInfo.setTitle(rendition.getTitle());
|
|
renditionInfo.setLength(rendition.getBigLength());
|
|
renditionInfos.add(renditionInfo);
|
|
}
|
|
info.setRenditionInfos(renditionInfos);
|
|
}
|
|
|
|
// relationships
|
|
setRelaionshipsToObjectInfo(object, info);
|
|
|
|
// global settings
|
|
info.setHasAcl(true);
|
|
info.setSupportsDescendants(true);
|
|
info.setSupportsFolderTree(true);
|
|
}
|
|
|
|
return info;
|
|
}
|
|
|
|
private void setRelaionshipsToObjectInfo(ObjectData object, ObjectInfoImpl info)
|
|
{
|
|
info.setRelationshipSourceIds(null);
|
|
info.setRelationshipTargetIds(null);
|
|
|
|
List<ObjectData> relationships = object.getRelationships();
|
|
if (relationships != null && relationships.size() > 0)
|
|
{
|
|
List<String> sourceIds = new ArrayList<String>();
|
|
List<String> targetIds = new ArrayList<String>();
|
|
for (ObjectData relationship : relationships)
|
|
{
|
|
String sourceId = getIdProperty(relationship, PropertyIds.SOURCE_ID);
|
|
String targetId = getIdProperty(relationship, PropertyIds.TARGET_ID);
|
|
if (object.getId().equals(sourceId))
|
|
{
|
|
sourceIds.add(relationship.getId());
|
|
}
|
|
if (object.getId().equals(targetId))
|
|
{
|
|
targetIds.add(relationship.getId());
|
|
}
|
|
}
|
|
if (sourceIds.size() > 0)
|
|
{
|
|
info.setRelationshipSourceIds(sourceIds);
|
|
}
|
|
if (targetIds.size() > 0)
|
|
{
|
|
info.setRelationshipTargetIds(targetIds);
|
|
}
|
|
}
|
|
}
|
|
|
|
// --------------------------------------------------------
|
|
|
|
private void checkRepositoryId(String repositoryId)
|
|
{
|
|
if (!connector.getRepositoryId().equals(repositoryId))
|
|
{
|
|
throw new CmisObjectNotFoundException("Unknown repository '" + repositoryId + "'!");
|
|
}
|
|
}
|
|
|
|
private Charset getEncoding(File tempFile, String mimeType)
|
|
{
|
|
Charset encoding = null;
|
|
|
|
try
|
|
{
|
|
InputStream tfis = new BufferedInputStream(new FileInputStream(tempFile));
|
|
ContentCharsetFinder charsetFinder = connector.getMimetypeService().getContentCharsetFinder();
|
|
encoding = charsetFinder.getCharset(tfis, mimeType);
|
|
tfis.close();
|
|
} catch (Exception e)
|
|
{
|
|
throw new CmisStorageException("Unable to read content: " + e.getMessage(), e);
|
|
}
|
|
|
|
return encoding;
|
|
}
|
|
|
|
private File copyToTempFile(ContentStream contentStream)
|
|
{
|
|
File tempFile = null;
|
|
|
|
if (contentStream == null)
|
|
{
|
|
return tempFile;
|
|
}
|
|
|
|
int bufferSize = 40 * 1014;
|
|
long count = 0;
|
|
|
|
try
|
|
{
|
|
tempFile = TempFileProvider.createTempFile("cmis", "content");
|
|
if (contentStream.getStream() != null)
|
|
{
|
|
OutputStream out = new BufferedOutputStream(new FileOutputStream(tempFile), bufferSize);
|
|
//InputStream in = new BufferedInputStream(contentStream.getStream(), bufferSize);
|
|
// Temporary work around for bug in InternalTempFileInputStream which auto closes during read
|
|
// BufferedInputStream subsequent use of available() throws an exception.
|
|
InputStream in = contentStream.getStream();
|
|
|
|
byte[] buffer = new byte[bufferSize];
|
|
int i;
|
|
while ((i = in.read(buffer)) > -1)
|
|
{
|
|
out.write(buffer, 0, i);
|
|
count += i;
|
|
}
|
|
|
|
in.close();
|
|
out.close();
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
removeTempFile(tempFile);
|
|
throw new CmisStorageException("Unable to store content: " + e.getMessage(), e);
|
|
}
|
|
|
|
if (contentStream.getLength() > -1 && contentStream.getLength() != count)
|
|
{
|
|
removeTempFile(tempFile);
|
|
throw new CmisStorageException(
|
|
"Expected " + contentStream.getLength() + " bytes but retrieved " + count + "bytes!");
|
|
}
|
|
|
|
return tempFile;
|
|
}
|
|
|
|
private void removeTempFile(File tempFile)
|
|
{
|
|
if (tempFile == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
tempFile.delete();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
// ignore - file will be removed by TempFileProvider
|
|
}
|
|
}
|
|
}
|