mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-06-30 18:15:39 +00:00
35366: Fix for ALF-13542 - Notification is not displayed, when you try to create duplicate user. 35538: Merged BRANCHES/DEV/CLOUD1 to BRANCHES/DEV/V4.0-BUG-FIX: (pre-req for ALF-13791) 35410: Merged BRANCHES/DEV/THOR1 to BRANCHES/DEV/CLOUD1: - fix merge issue (compilation fix) 35443: Merge build/test fix (record-only) 35463: Merged BRANCHES/DEV/THOR1 to BRANCHES/DEV/CLOUD1: 30194: Merged BRANCHES/DEV/THOR0 to BRANCHES/DEV/THOR1: 29718: ALF-6029: Additional MT fix to force default tenant 29719: THOR-7: Create tenant 35541: Fix for ALF-13723 SOLR does not include the same query unit tests as lucene - added base tests 35547: Merged BRANCHES/DEV/CLOUD1 to BRANCHES/DEV/V4.0-BUG-FIX: (ALF-13791) 35511: Merged BRANCHES/DEV/THOR1 to BRANCHES/DEV/CLOUD1: 30252: Merged BRANCHES/DEV/THOR0 to BRANCHES/DEV/THOR1: 29763: THOR-107: MT-aware immutable singletons 29766: THOR-107: MT-aware immutable singletons 29768: THOR-31: MT-aware shared cache 29770: THOR-107: MT-aware immutable singletons 35512: Merged BRANCHES/DEV/THOR1 to BRANCHES/DEV/CLOUD1: 30253: Merged BRANCHES/DEV/THOR0 to BRANCHES/DEV/THOR1: 29771: THOR-31: MT-aware shared cache 29777: THOR-107: MT-aware immutable singletons 29786: THOR-107: MT-aware immutable singletons 29787: THOR-31: MT-aware shared cache (fix MultiTNodeServiceInterceptorTest) 29799: THOR-107: MT-aware immutable singletons 35513: Merge build/test fix 35516: Merged BRANCHES/DEV/THOR0 to BRANCHES/DEV/CLOUD1: 30026: THOR-5: tenant-aware caches 35517: Merged BRANCHES/DEV/THOR1 to BRANCHES/DEV/CLOUD1: 30260: Merged BRANCHES/DEV/THOR0 to BRANCHES/DEV/THOR1: (core) 29860: THOR-73: prep for HEAD sync/merge-forward 29866: THOR-73: prep for HEAD sync/merge-forward 30026: THOR-5: tenant-aware caches 35520: Merged BRANCHES/DEV/THOR1 to BRANCHES/DEV/CLOUD1: 30297: THOR-73: Line-endings only 30298: THOR-73: Line-endings only 30300: THOR-73: fix ActivitiWorkflowServiceIntegrationTest 30302: THOR-73: fix SubscriptionServiceActivitiesTest 35528: Merged BRANCHES/DEV/THOR1 to BRANCHES/DEV/CLOUD1: 30459: THOR-156: prep - consolidate/improve get current user's tenant domain 30469: Fix bootstrap config check when running unit tests (where one tenant already exists) (partial merge only) 35565: MT: fix update tenant entity - pre-req for ALF-13757 35567: Fix merge fallout (compile error) 35569: ALF-13757: MT - minor patch to migrate existing tenants, if any (when upgrading) 35592: Merged BRANCHES/DEV/THOR1_SPRINTS to BRANCHES/DEV/V4.0-BUG-FIX: (ALF-13791) 34153: Minor: THOR-5: MT-aware immutable singletons (spp/vti) 35598: ALF-11459: Added null-check on in-flight process diagram-generation to prevent error when running headless 35604: ALF-13426 Transformation: DOCX conversion failure <<< Fix split into two parts to make merge of this general part to 3.4.10 simpler. >>> <<< The second part contains 4.0.x specific changes. >>> - Change to ContentServiceImpl to fail over to other available transformers on error (can be turned off with global property content.transformer.failover=false). 35605: ALF-13426 Transformation: DOCX conversion failure <<< Second part >>> - Remove explicit transformation sections for OOXML (added in 4.0.1 ALF-12461) as these are stopping other transformers from being used. Was done originally as a copy paste from another bean that needed an explicit section. - The combination of allowing other transformers (that were used prior to 4.0.1) and fail over from OOXML to these transformers allows to docx fixes that do and do not contain an embedded image to be transformed to png. 35608: fix build 35609: Merged V3.4-BUG-FIX (3.4.10) to V4.0-BUG-FIX (4.0.2) RECORD ONLY 35607: Merged V4.0-BUG-FIX (4.0.2) to V3.4-BUG-FIX (3.4.10) 35604: ALF-13426 Transformation: DOCX conversion failure <<< Fix split into two parts to make merge of this general part to 3.4.10 simpler. >>> <<< The second part contains 4.0.x specific changes. >>> - Change to ContentServiceImpl to fail over to other available transformers on error (can be turned off with global property content.transformer.failover=false). 35619: ALL LANGUAGES: Translation updates based on EN r35407 35630: Merged HEAD to BRANCHES/DEV/V4.0-BUG-FIX: 34289: Upgrading JUnit lib to 4.10 to get full Rules support. 34317: Some initial documentation on JUnit Rules samples. 34328: More JUnit rules fun. Added a new rule to help with the creation and automatic cleanup of temporary test nodes. 34777: Added enhancement to TemporaryNodes rule to allow for dummy content. 34805: Added a convenience method to the ApplicationContextInit @Rule to allow for easier spring overriding in test code. 35621: Merged BRANCHES/DEV/CLOUDSYNCLOCAL2 to HEAD: 35620: More JUnit Rules Enhancements, covering well known nodes and easier context loading 35631: Fixing some Eclipse junit/lib dependencies which had become out of date - seemingly before my pervious commit (35630). 35640: Fix for ALF-10085 "Adding/removing CMIS Relationship changes last modified date of source object" 35647: Merged BRANCHES/DEV/DAM/V4.0-BUG-FIX-35588 to BRANCHES/DEV/V4.0-BUG-FIX: 35589: Creating new branch from $FROM 35591: Merged BRANCHES/DEV/DAM/V4.0-BUG-FIX-35195 to BRANCHES/DEV/DAM/V4.0-BUG-FIX-35588: 35196: Creating new branch from $FROM 35338: ALF-13734: Move Additional DocumentList Methods to DocumentListViewRenderer - Moved tooltip to simple viewRenderer 35340: ALF-13734: Move Additional DocumentList Methods to DocumentListViewRenderer - Moved fnRenderCellSelected logic to DocumentListViewRenderer - Moved fnRenderCellStatus logic to DocumentListViewRenderer - Moved fnRenderCellDescription logic to DocumentListViewRenderer - Moved fnRenderCellActions logic to DocumentListViewRenderer 35346: ALF-13734: Move Additional DocumentList Methods to DocumentListViewRenderer - Moved onEventHighlightRow logic to DocumentListViewRenderer - Moved onEventUnhighlightRow logic to DocumentListViewRenderer - Moved onActionShowMore logic to DocumentListViewRenderer - Minor private method renaming 35427: ALF-13734: Move Additional DocumentList Methods to DocumentListViewRenderer - Changed check for display of metadata banners and lines to more explicit bannerView and lineView properties which are set to the viewRenderer's name by default, but can now more easily be overridden 35503: ALF-13734: Move Additional DocumentList Methods to DocumentListViewRenderer - Renamed bannerView property to more specific metadataBannerViewName - Renamed lineView property to more specific metadataLineViewName 35583: ALF-13734: Move Additional DocumentList Methods to DocumentListViewRenderer - Added rowClassName property to make finding the row easier in cases where an event trigger element might not be the row itself - Added check for expected row element class name in getDataTableRecordIdFromRowElement, if not present trying getAncestorByClassName with rowClassName property - Moved onFileRenamed to DocumentListViewRenderer - Changed fnActionHandler to use getDataTableRecordIdFromRowElement rather than target.offsetParent - Changed onLikes to use getDataTableRecordIdFromRowElement rather than assume the row parameter is the correct element - Changed onFavourite to use getDataTableRecordIdFromRowElement rather than assume the row parameter is the correct element 35610: ALF-13734: Move Additional DocumentList Methods to DocumentListViewRenderer - Changed method of grabbing container element in selectFiles to use parentElementIdSuffix from current viewRenderer 35650: Fix for ALF-13813 SOLR fails for fuzzy queries 35651: Fix tests for ALF-13813 SOLR fails for fuzzy queries More for ALF-13723 SOLR does not include the same query unit tests as lucene - added tests for Alfresco fts run via the request handler - fixed fuzzy query tests so far ... - report queries that generate errors 35664: ALF-13294 - CIFS: When versionable aspect is active, using the Microsoft Word for Mac 2008 option "always create a backup copy" leads to document versions loss 35679: Fix DataList QName hard-codings by pulling out to a proper Model Java Constants Interface 35689: Add the NameSpace constants for the Links model 35699: Merged BRANCHES/DEV/CLOUDSYNCLOCAL2 to BRANCHES/DEV/V4.0-BUG-FIX: 35698: New WebScript to provide the Share View URL for a given NodeRef (based on the Node Type and SysAdminParams) 35716: Make overriding just the Share URL easier (needed for Cloud installs) 35741: ALF-13819 Remove description+template for a controller-less webscript that was committed by mistake in v3.4 35765: Fixed version of junit.jar in build files 35772: ALF-1994 - Allow user defined white-list of HTML tags for HTML sanitisation process. Spring config added for tags and attributes. 35781: Fix for MySQL part of ALF-13150: Performance of Purging Empty Transactions (like 10M) ALF-13839: MySQL: "Failed to purge txns" from DeletedNodeCleanupWorker - Added MySQL override of the NodeDAO for this call with a dedicated DELETE ... JOIN ... for MySQL 35784: Fix for ALF-13845 SOLR "alfresco" queries are not cached correctly 35785: More for ALF-13723 SOLR does not include the same query unit tests as lucene - duplicated sort and AFTS tests from the lucene sub-system - run queries via request handler - addded new locale tests for d:text ordering 35805: ALF-13828 Method name typo, should be getThumbnailDefinitions not getThumbnailDefintions. (Old method retained, @deprecated, for backwards compatibility) 35806: More debug to setFileInformation 35836: Fix for ALF-13794 Mismatch in SOLRAPIClient and NodeContentGet webscript causes content of type d:content not to get indexed 35862: Fix for ALF-13826 Solr CMIS Query After Delete a Node Throws CmisRuntimeException: Node does not exist - make appropriate methods aware of node existence.... 35867: ALF-13886 Certain errors may lead to no conn model object being available, so check it is there before using it to render the "Return to folder" link 35901: ALF-13474 possibility of deleting compleded workflows + explorer ui cancel action fix 35923: Fix for ALF-13724 Share folder permission management - changes to parent/child folders not accurately reflected 35936: More for ALF-13723 SOLR does not include the same query unit tests as lucene - tests for mltext localised collation 35944: BufferedContentDiskDriver needs to use deviceName and sessionKey to make it unique rather than userName 35949: ALF-13755: MT is configured (but not enabled) by default - note: also related to THOR-248 (effectively means that r31407 becomes a merge record-only) 35951: Merged BRANCHES/DEV/THOR1 to BRANCHES/DEV/V4.0-BUG-FIX: 34107: record-only (follow on to r35949 - see ALF-13755 / THOR-248) 35953: ALF-12792 - Creation Date and Modification Date initialization for open files. 35968: Follow up to fix for ALF-13839: MySQL: "Failed to purge txns" from DeletedNodeCleanupWorker - Sanity check highlighted transactional resource block in the database - Each cleanup runs its own transactions as required now - See also ALF-13150: Performance of Purging Empty Transactions suffers if the number of unused transactions grows too large (like 10M) 35970: Merged BRANCHES/DEV/DAM/V4.0-BUG-FIX-35924 to BRANCHES/DEV/V4.0-BUG-FIX: 35925: Creating new branch from BRANCHES/DEV/V4.0-BUG-FIX 35966: ALF-13912: Move DocumentList.onHighlightFile UI Logic to DocumentListViewRenderer - Moved DocumentList.onHighlightFile logic to DocumentListViewRenderer - Added DocumentListViewRenderer.getRowElementFromDataTableRecord and DocumentListViewRenderer.getRowSelectElementFromDataTableRecord - Changed onHighlightFile to call those new getRow* methods for easier reuse in view renderer extensions 35979: ALF-10278, ALF-13902: Ending task now done with the right assignee when unassigned (eg. not claimed from pool) or when workflow-owner completes the task assigned to someone else, without claiming first 35981: Fix for ALF-12670 - An exception occurs during creation wiki page Changed Wiki title field limit to the 100 char limit imposed by QName which unfortunately is used by the underlying service to store the field title. 35991: ALF-13901: Incorrect workflow-history gathering/displaying on uncompleted tasks in ended parallel multi-instance activity 35993: ALF-10278, ALF-13902: Fixed failing test (was not using AuthenticationUtil for test-user) 36001: BDE-69: create test-minimal and continuous-minimal Ant targets 36004: Fixes for: ALF-12813 - jsonUtils.toJSONString mangles up Associative Arrays - Added support for nested Java Map/List to jsonUtils ALF-13647 - the first time a ICAL calendar URL is called with kerberos SSO a JSESSIONID cookie is not sent by the client, request fails with a 500 Internal server error - Support for "negotiate" HTTP auth header and general improvements to that area ALF-13877 - Invalid WebScript URLs cause ERROR-level exception stacks - DEBUG only output for "missing" webscripts and invalid API call URLs 36014: ALF-13844: XSLT Filtering Not 100% Secure - added more namespaces to the security filter. - verified that include/import uses the security filter. 36018: ALF-13609: Enterprise installers lay down sample site and users -Added feature to SiteLoadPatch to disable loading. -Added property "disable.sample.site". Set property (system or otherwise) disable.sample.site=true to skip loading the sample site on a new installation. 36031: debug improvement. 36039: ALF-13779: isPooled() implemented correctly now 36044: ALF-13770: Merged V3.4-BUG-FIX (3.4.10) to V4.0-BUG-FIX (4.0.2) 36043: ALF-13769: Merged V3.4.8 (3.4.8.7) to V3.4-BUG-FIX (3.4.10) 35776: ALF-11535 Home Folder Synchronizer fails when destination folder already exists - Don't move home folders that are the same as the provider's root folder or even above it! If the same, these tend to be shared folders. If above, this indicates that an LDAP sync has corrupted the original provider name and has hence changed what we think is the root folder! 36046: ALF-13745: Merged V3.4-BUG-FIX (3.4.10) to V4.0-BUG-FIX (4.0.2) <<< Also added placeholder thumbnails (copies if docx, pptx and xlsx which in turn appear to be copies of the 2003 doc, ppt and xls) >>> 36041: ALF-13667 Additional OpenOffice mimetypes to be added to the mime-type maps - Added mimetypes for docm dotx dotm pptm ppsx ppsm potx potm ppam sldx sldm xltm xlsm xltm xlam xlsb - Added transformation limits to avoid very long running tasks. - Disable Jod and OpenOffice transformers via PDFBox for new types to txt, as there are better options with the exception of potm and xlsb that can only be done by Office. - TransformerDebug include max source size in available transformer list 35958: ALF-13745 Add Support for Microsoft Word File Format DOCM << General TransformationOptionLimits change >> - Addition of TransformationOptionLimitsMap to make it simpler to add lots of TransformationOptionLimits. Only one per line rather than about 10 - Needed for this JIRA as lots of limits are needed - Changes to transformerDebug to make it more obvious which transformers are excluded 36047: Fix for ALF-13925 - UsernamePropertyDecorator incorrectly handles displayName construction 36048: More for ALF-13723 SOLR does not include the same query unit tests as lucene - tests and fixes for internal fields 36061: Fix remoteapi tests by putting back repository tests before, where they belong 36064: ALF-13682 'View Process Diagram' not working if auditing is turned on - modified Auditable annotation on the getWorkflowImage() method which was returning an InputStream so we would not consume the input again. - modified AuditMethodInterceptor to ignore any InputStream and OutputStream values. Implemented as a list of non aubitable classes. Refactored generation of auditable arguments and return value to a method rather than two almost identical in-line copies. 36065: ALF-13756: MT - replace Tenant attributes with Tenant table - update schema comp files for x5 DBs - note: will need to be tested in DB build plans (via schema comp -> when fail on error is enabled) 36066: ALF-13609: Enterprise installers lay down sample site and users -Removed extraneous line of code. -Renamed boolean to "disabled" and associated accessors. -Added property "sample.site.disabled=false" to repository.properties. -To disable loading of the sample site on a new installation, set property (system or otherwise) "sample.site.disabled=true" 36080: Merged BRANCHES/DEV/DAM/V4.0-BUG-FIX-36070 to BRANCHES/DEV/V4.0-BUG-FIX: 36071: Creating new branch from BRANCHES/DEV/V4.0-BUG-FIX 36079: Merged BRANCHES/DEV/DAM/V4.0-BUG-FIX-35924 to BRANCHES/DEV/DAM/V4.0-BUG-FIX-36070: 36069: ALF-13935: Move DocumentList Upload Indicators and Instructions to DocumentListViewRenderer - Created renderEmptyDataSourceHtml method in DocumentListViewRenderer which contains the view logic previously in _setupDataSource - Created _setEmptyDataSourceMessage which actually appends the constructed empty HTML instructions for cases where extensions simply want the same instructions but in a different container - Added firing of Bubbling event postSetupViewRenderers at the end of _setupViewRenderers since all viewRenderers now have to be registered before _setupDataSource is called 36085: More for ALF-13723 SOLR does not include the same query unit tests as lucene - internal fields - paging - security filters 36089: ALF-11725: Replication document with comment fails due to integrity exception - updated script transfer service. 36094: ALF-11725 : config change. 36098: ALF-13719: Javascript addAspect(aspect, properties) does not apply cm:autoVersionOnUpdateProps property value 36105: SESURF-102: Fix dependency handling when use-checksum-dependencies is not enabled. 36107: Tweak wiki page create/update logic, to handle clearing the tags when updating a page when all tags are removed (ALF-10979) 36109: ALF-7874 MimeType definitions for Adobe AfterEffects files 36110: ALF-7874 Upgrade Tika for improved detection of Adobe Premier and AfterEffects 36112: ALF-7874 MimeType definition addition for Adobe Premier files 36133: Merged DEV to V4.0-BUG-FIX 36130: ALF-13988 : apply_amps script no longer works on Mac OSX apply_amps.sh was corrected to resolve "readlink -f" Mac OS problem. 36135: ALF-12330: Editing of completed task now redirects to referring page (if available) + transition-buttons not rendered on completed tasks 36141: Merged V3.4-BUG-FIX to V4.0-BUG-FIX 35641: ALF-13452: Open office startup from Java not working on OSX - Fix from Bitrock in combination with new wrapper in BINARIES 35687: ALF-13520: alfresco.log file ending up in system32 directory - Not anymore! 35736: ALF-13751: Reduce over-agressive traversal of child associations when detecting cyclic groups in LDAP sync - Recurse upwards to topmost parent then recurse downwards - No need to recurse upwards and downwards on every recursion step! 35987: Merged DEV to V3.4-BUG-FIX 35984: ALF-11850 WCM - Incorrect message when copying/cutting assets within a Web Project 1. In ClipboardBean.addClipboardNode(NodeRef ref, NodeRef parent, ClipboardStatus mode) was added check whether the node in the AVM. 2. In webclient.properties was added node_added_clipboard_avm property. 36049: Fix for ALF-9662 To allow admin user to view dashboard of moderated site. 36050: Fix for ALF-13843 - Content creation silently fails when it's being created with already existent name. 36054: Fix for ALF-13231 - Message 'Failure' on workflow cancelation 36055: Fix for ALF-13926 - Intranet compatibility options override share's settings 36102: Merged BRANCHES/V3.4 to BRANCHES/DEV/V3.4-BUG-FIX 36097: Fix for ALF-13976 - 404 error handling in Share no longer correctly receives JSON response. OKed by DaveW. 36103: ALF-13578 : CIFS AlfJLANWorker threads (concurrency) - server not responding 36137: Merged V3.4 to V3.4-BUG-FIX 35433: ALF-13021: Folder deletion from Editorial not deleting from Live folder automatically - Fix by Valery - Needs further work for 4.0.x 35488: ALF-13718: Full reindex performance on SQL Server - ORDER BY on child assoc query changed to only include ID (with Derek's permission) - ADMLuceneIndexerImpl altered to not use batch loading in getChildAssocs so as not to blow the transactional caches when reindexing a large hierarchy - ADMLuceneIndexerImpl altered so that it only checks for the existence of child associations when 'lazily' creating parent containers - ADMLuceneTest corrected (with Andy's permission) so that this doesn't throw the unit test out 35505: ALF-13718: Corrected ADMLuceneCategoryTest to clear the 'real' index before creating a fake 'test' index 35809: Merged DEV to V3.4 35800: ALF-10353 : Internet Explorer hangs when using the object picker with a larger number of documents YUI library was modified to use chunked unloading of listeners via a series of setTimeout() functions in event.js for IE 6,7,8. 36101: ALF-13978: Merged V4.0-BUG-FIX to V3.4 36014: ALF-13844: XSLT Filtering Not 100% Secure - added more namespaces to the security filter. - verified that include/import uses the security filter. 36108: ALF-13978: Fixed compilation errors 36129: Merged DEV to V3.4 36123: ALF-13951 : It's impossible to customize dashboard in Alfresco Share A yui-2.8.1-patched library contains a fix for ALF-10353. 36142: Merged V3.4-BUG-FIX to V4.0-BUG-FIX (RECORD ONLY) 35432: ALF-13762: Merged V4.0-BUG-FIX to V3.4-BUG-FIX 35366: Fix for ALF-13542 - Notification is not displayed, when you try to create duplicate user. 35593: Merged BRANCHES/V3.4 to BRANCHES/DEV/V3.4-BUG-FIX: 35375: Fix for ALF-13711: "Hidden Aspect applied to Mac powerpoint files." - re-instated previous hidden aspect behaviour - unit tests - also fixed cascade behaviour 36144: Merged V4.0 to V4.0-BUG-FIX 35918: 31473: -- initial commit for ALF-11027 -- enables CE / EE deployment of artifacts to a maven repository -- added necessary ant build files and build properties. -- also added a README 31474: -- added .project to svn:ignore 32534: -- added missing artifacts and fixed wrong ones -- added possibility of custom artifacts labeling (by adding -Dmaven.custom.label), e.g. to allow snapshot / release deployments from working branches -- TODO: document required ~/.m2/settings.xml 32582: -- renamed alfresco-datamodel to alfresco-data-model -- fixed release/snapshot and custom version labeling -- tested with Community, enterprise build undergoing (removed distribute-extras as pre-requisite) 32610: -- tested enterprise only deployment -- removed tabs -- fixed property placeholding -- tested all artifacts 32611: -- added maven-ant-tasks library to automatically load ant maven tasks without dependencies on the ant installation -- added typedef in the main maven.xml 35250: -- reworking on ALF-11027 to enable automated deployment of artifacts to the Maven repo -- following conversations with DaveW implemented the following: - removed classifier (just rely on different groupId, org.alfresco for Community and org.alfresco.enterprise for Enterprise) - Added debugging lines to make sure proper repo / groupId configuration is picked up - enabled SNAPSHOT/RELEASE deployment for both community and enterprise -- updated README-maven-deploy.txt with all instructions on how to run the build 35388: -- added references to Maven settings.xml in the BINARIES as discussed with DaveW 35648: [ALF-11027] Since artifact:mvn does not support settingsFile attribute, switching to embedded command line -gs parameter to specify a custom settings.xml location 35649: [ALF-11027] Since artifact:mvn does not support settingsFile attribute, switching to embedded command line -gs parameter to specify a custom settings.xml location 35652: [ALF-11027] artifact:mvn uses an older Maven version which command line switch is -s instead of -gs 35775: [ALF-11027] Removed DoD and Kofax deployment from enteprise deployment procedure 35783: [ALF-11027] removing custom README and added documentation in line of the tw maven.xml files 35793: Fix comment syntax: no -- allowed there 35802: [ALF-11027] Removed calls to DoD targets 35810: [ALF-11027] Introducing a maven-build-deploy goal to build and deploy at once, maven-deploy now "just does the job 35822: [ALF-11027] Moved the maven setup steps to a maven-env-prerequisites separate target, so it gets executed earlier and defines the task 35851: [ALF-11027] Move targets around to fix the regular, non-continuous build 35894: [ALF-11027] Add maven.do.deploy variable, to control maven deployment from bamboo using parameterised plan 35896: [ALF-11027] Using Bamboo Plan Variables properly 35899: [ALF-11027] Use Bamboo variable to specify release vs snapshot rather than deployment or not 35905: [ALF-11027] Upload source and javadoc jars into Maven repo as well 35912: Upgrade maven-deploy-plugin to 2.7, to be able to deploy Javadoc and Source jars as well 35950: ALF-11027: Fix typo in jlan-embed deployment, removed svn revision from version, removed deployment of jmx-dumper 36145: Merged V4.0 to V4.0-BUG-FIX (RECORD ONLY) 34612: Merged V4.0-BUG-FIX to V4.0 ALF-12740: Update to previous fix (only apply to IE8 and below) 34618: Merged V4.0-BUG-FIX to V4.0 34474: ALF-13169 Tomcat fails to shutdown - fix non daemon Timers 34637: Merged BRANCHES/DEV/V4.0-BUG-FIX to BRANCHES/V4.0 (4.0.1) 34636: Fix for ALF-13365 SOLR: Recently modified docs dashlet sorts incorrectly 34690: MERGE V4.0_BUG-FIX to V4.0 34226 : ALF-12780 Mac OS X Lion 10.7.2: Editing a document via CIFS and TextEdit removes versionable aspect from this file 34716: Merged V4.0-BUG-FIX to V4.0 34715: Fix for __ShowDetails desktop action returned URL is truncated if hostname too long. ALF-13202. git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@36155 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
1656 lines
58 KiB
Java
1656 lines
58 KiB
Java
/*
|
|
* Copyright (C) 2009-2011 Alfresco Software Limited.
|
|
*
|
|
* This file is part of Alfresco
|
|
*
|
|
* Alfresco is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Alfresco is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
package org.alfresco.repo.transfer;
|
|
|
|
import java.io.BufferedOutputStream;
|
|
import java.io.File;
|
|
import java.io.FileOutputStream;
|
|
import java.io.InputStream;
|
|
import java.io.OutputStream;
|
|
import java.io.OutputStreamWriter;
|
|
import java.io.Serializable;
|
|
import java.text.SimpleDateFormat;
|
|
import java.util.Collections;
|
|
import java.util.Date;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
|
|
import javax.xml.parsers.SAXParser;
|
|
import javax.xml.parsers.SAXParserFactory;
|
|
|
|
import org.alfresco.model.ContentModel;
|
|
import org.alfresco.repo.cache.SimpleCache;
|
|
import org.alfresco.repo.content.ContentServicePolicies;
|
|
import org.alfresco.repo.copy.CopyBehaviourCallback;
|
|
import org.alfresco.repo.copy.CopyDetails;
|
|
import org.alfresco.repo.copy.CopyServicePolicies;
|
|
import org.alfresco.repo.copy.DefaultCopyBehaviourCallback;
|
|
import org.alfresco.repo.lock.JobLockService;
|
|
import org.alfresco.repo.lock.LockAcquisitionException;
|
|
import org.alfresco.repo.node.NodeServicePolicies;
|
|
import org.alfresco.repo.policy.Behaviour.NotificationFrequency;
|
|
import org.alfresco.repo.policy.BehaviourFilter;
|
|
import org.alfresco.repo.policy.ClassPolicyDelegate;
|
|
import org.alfresco.repo.policy.JavaBehaviour;
|
|
import org.alfresco.repo.policy.PolicyComponent;
|
|
import org.alfresco.repo.security.authentication.AuthenticationUtil;
|
|
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
|
|
import org.alfresco.repo.tenant.TenantService;
|
|
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
|
|
import org.alfresco.repo.transaction.RetryingTransactionHelper;
|
|
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
|
import org.alfresco.repo.transfer.ChangeCapturingProgressMonitor.TransferChangesRecord;
|
|
import org.alfresco.repo.transfer.manifest.TransferManifestProcessor;
|
|
import org.alfresco.repo.transfer.manifest.XMLTransferManifestReader;
|
|
import org.alfresco.repo.transfer.requisite.XMLTransferRequsiteWriter;
|
|
import org.alfresco.service.cmr.action.Action;
|
|
import org.alfresco.service.cmr.action.ActionService;
|
|
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
|
import org.alfresco.service.cmr.repository.NodeRef;
|
|
import org.alfresco.service.cmr.repository.NodeService;
|
|
import org.alfresco.service.cmr.repository.StoreRef;
|
|
import org.alfresco.service.cmr.rule.RuleService;
|
|
import org.alfresco.service.cmr.search.ResultSet;
|
|
import org.alfresco.service.cmr.search.SearchService;
|
|
import org.alfresco.service.cmr.transfer.TransferException;
|
|
import org.alfresco.service.cmr.transfer.TransferProgress;
|
|
import org.alfresco.service.cmr.transfer.TransferProgress.Status;
|
|
import org.alfresco.service.cmr.transfer.TransferReceiver;
|
|
import org.alfresco.service.cmr.transfer.TransferServicePolicies;
|
|
import org.alfresco.service.cmr.transfer.TransferServicePolicies.BeforeStartInboundTransferPolicy;
|
|
import org.alfresco.service.cmr.transfer.TransferServicePolicies.OnEndInboundTransferPolicy;
|
|
import org.alfresco.service.cmr.transfer.TransferServicePolicies.OnStartInboundTransferPolicy;
|
|
import org.alfresco.service.cmr.transfer.TransferVersion;
|
|
import org.alfresco.service.descriptor.Descriptor;
|
|
import org.alfresco.service.descriptor.DescriptorService;
|
|
import org.alfresco.service.namespace.NamespaceService;
|
|
import org.alfresco.service.namespace.QName;
|
|
import org.alfresco.service.namespace.RegexQNamePattern;
|
|
import org.alfresco.service.transaction.TransactionService;
|
|
import org.alfresco.util.PropertyCheck;
|
|
import org.apache.commons.logging.Log;
|
|
import org.apache.commons.logging.LogFactory;
|
|
import org.springframework.util.FileCopyUtils;
|
|
|
|
/**
|
|
* The Repo Transfer Receiver is the "Back-End" for transfer subsystem.
|
|
* <p>
|
|
* Provides the implementation of the transfer commands on the destination repository.
|
|
* <p>
|
|
* Provides callback handlers for Aliens and Transferred Aspects.
|
|
* <p>
|
|
* Calls transfer policies.
|
|
* <p>
|
|
* Co-ordinates locking and logging as the transfer progresses.
|
|
*
|
|
* @author brian
|
|
*/
|
|
public class RepoTransferReceiverImpl implements TransferReceiver,
|
|
NodeServicePolicies.OnCreateChildAssociationPolicy,
|
|
NodeServicePolicies.BeforeDeleteNodePolicy,
|
|
NodeServicePolicies.OnRestoreNodePolicy,
|
|
NodeServicePolicies.OnMoveNodePolicy,
|
|
ContentServicePolicies.OnContentUpdatePolicy
|
|
{
|
|
/**
|
|
* This embedded class is used to push requests for asynchronous commits onto a different thread
|
|
*
|
|
* @author Brian
|
|
*
|
|
*/
|
|
public class AsyncCommitCommand implements Runnable
|
|
{
|
|
|
|
private String transferId;
|
|
private String runAsUser;
|
|
|
|
public AsyncCommitCommand(String transferId)
|
|
{
|
|
this.transferId = transferId;
|
|
this.runAsUser = AuthenticationUtil.getFullyAuthenticatedUser();
|
|
}
|
|
|
|
public void run()
|
|
{
|
|
RunAsWork<Object> actionRunAs = new RunAsWork<Object>()
|
|
{
|
|
public Object doWork() throws Exception
|
|
{
|
|
return transactionService.getRetryingTransactionHelper().doInTransaction(
|
|
new RetryingTransactionCallback<Object>()
|
|
{
|
|
public Object execute()
|
|
{
|
|
commit(transferId);
|
|
return null;
|
|
}
|
|
}, false, true);
|
|
}
|
|
};
|
|
AuthenticationUtil.runAs(actionRunAs, runAsUser);
|
|
}
|
|
|
|
}
|
|
|
|
private final static Log log = LogFactory.getLog(RepoTransferReceiverImpl.class);
|
|
|
|
private static final String MSG_FAILED_TO_CREATE_STAGING_FOLDER = "transfer_service.receiver.failed_to_create_staging_folder";
|
|
private static final String MSG_ERROR_WHILE_STARTING = "transfer_service.receiver.error_start";
|
|
private static final String MSG_TRANSFER_TEMP_FOLDER_NOT_FOUND = "transfer_service.receiver.temp_folder_not_found";
|
|
private static final String MSG_TRANSFER_LOCK_UNAVAILABLE = "transfer_service.receiver.lock_unavailable";
|
|
private static final String MSG_INBOUND_TRANSFER_FOLDER_NOT_FOUND = "transfer_service.receiver.record_folder_not_found";
|
|
|
|
private static final String MSG_ERROR_WHILE_ENDING_TRANSFER = "transfer_service.receiver.error_ending_transfer";
|
|
private static final String MSG_ERROR_WHILE_STAGING_SNAPSHOT = "transfer_service.receiver.error_staging_snapshot";
|
|
private static final String MSG_ERROR_WHILE_STAGING_CONTENT = "transfer_service.receiver.error_staging_content";
|
|
private static final String MSG_NO_SNAPSHOT_RECEIVED = "transfer_service.receiver.no_snapshot_received";
|
|
private static final String MSG_ERROR_WHILE_COMMITTING_TRANSFER = "transfer_service.receiver.error_committing_transfer";
|
|
private static final String MSG_ERROR_WHILE_GENERATING_REQUISITE = "transfer_service.receiver.error_generating_requisite";
|
|
private static final String MSG_LOCK_TIMED_OUT = "transfer_service.receiver.lock_timed_out";
|
|
private static final String MSG_LOCK_NOT_FOUND = "transfer_service.receiver.lock_not_found";
|
|
private static final String MSG_TRANSFER_TO_SELF = "transfer_service.receiver.error.transfer_to_self";
|
|
private static final String MSG_INCOMPATIBLE_VERSIONS = "transfer_service.incompatible_versions";
|
|
|
|
private static final String SNAPSHOT_FILE_NAME = "snapshot.xml";
|
|
|
|
private NodeService nodeService;
|
|
private SearchService searchService;
|
|
private TransactionService transactionService;
|
|
private String transferLockFolderPath;
|
|
private String inboundTransferRecordsPath;
|
|
private String rootStagingDirectory;
|
|
private String transferTempFolderPath;
|
|
private ManifestProcessorFactory manifestProcessorFactory;
|
|
private BehaviourFilter behaviourFilter;
|
|
private ChangeCapturingProgressMonitor progressMonitor;
|
|
private ActionService actionService;
|
|
private TenantService tenantService;
|
|
private RuleService ruleService;
|
|
private PolicyComponent policyComponent;
|
|
private DescriptorService descriptorService;
|
|
private AlienProcessor alienProcessor;
|
|
private JobLockService jobLockService;
|
|
private TransferVersionChecker transferVersionChecker;
|
|
|
|
// note: cache is tenant-aware (if using EhCacheAdapter shared cache)
|
|
private SimpleCache<String, NodeRef> singletonCache; // eg. for transfer temp folder nodeRef
|
|
private final String KEY_TRANSFER_TEMP_NODEREF = "key.transferTempFolder.noderef"; // where temp files are stored
|
|
private final String KEY_INBOUND_TRANSFER_RECORDS_NODEREF = "key.inboundTransferRecordsFolder.noderef"; // where the destination side transfer report is generated
|
|
|
|
private ClassPolicyDelegate<BeforeStartInboundTransferPolicy> beforeStartInboundTransferDelegate;
|
|
private ClassPolicyDelegate<OnStartInboundTransferPolicy> onStartInboundTransferDelegate;
|
|
private ClassPolicyDelegate<OnEndInboundTransferPolicy> onEndInboundTransferDelegate;
|
|
|
|
/**
|
|
* Locks for the transfers in progress
|
|
* <p>
|
|
* TransferId, Lock
|
|
*/
|
|
private Map<String, Lock> locks = new ConcurrentHashMap<String, Lock>();
|
|
|
|
/**
|
|
* How many mS before refreshing the lock?
|
|
*/
|
|
private long lockRefreshTime = 60000;
|
|
|
|
/**
|
|
* How many times to retry to obtain the lock
|
|
*/
|
|
private int lockRetryCount = 2;
|
|
|
|
/**
|
|
* How long to wait between retries
|
|
*/
|
|
private long lockRetryWait = 100;
|
|
|
|
/**
|
|
* How long in mS to keep the lock before giving up and ending the transfer,
|
|
* possibly the client has terminated?
|
|
*/
|
|
private long lockTimeOut = 3600000;
|
|
|
|
public void init()
|
|
{
|
|
PropertyCheck.mandatory(this, "nodeService", nodeService);
|
|
PropertyCheck.mandatory(this, "searchService", searchService);
|
|
PropertyCheck.mandatory(this, "ruleService", ruleService);
|
|
PropertyCheck.mandatory(this, "actionService", actionService);
|
|
PropertyCheck.mandatory(this, "behaviourFilter", behaviourFilter);
|
|
PropertyCheck.mandatory(this, "tennantService", tenantService);
|
|
PropertyCheck.mandatory(this, "transactionService", transactionService);
|
|
PropertyCheck.mandatory(this, "transferLockFolderPath", transferLockFolderPath);
|
|
PropertyCheck.mandatory(this, "inboundTransferRecordsPath", inboundTransferRecordsPath);
|
|
PropertyCheck.mandatory(this, "rootStagingDirectory", rootStagingDirectory);
|
|
PropertyCheck.mandatory(this, "policyComponent", policyComponent);
|
|
PropertyCheck.mandatory(this, "descriptorService", descriptorService);
|
|
PropertyCheck.mandatory(this, "alienProcessor", alienProcessor);
|
|
PropertyCheck.mandatory(this, "jobLockService", getJobLockService());
|
|
PropertyCheck.mandatory(this, "transferVersionChecker", getTransferVersionChecker());
|
|
|
|
beforeStartInboundTransferDelegate = policyComponent.registerClassPolicy(TransferServicePolicies.BeforeStartInboundTransferPolicy.class);
|
|
onStartInboundTransferDelegate = policyComponent.registerClassPolicy(TransferServicePolicies.OnStartInboundTransferPolicy.class);
|
|
onEndInboundTransferDelegate = policyComponent.registerClassPolicy(TransferServicePolicies.OnEndInboundTransferPolicy.class);
|
|
|
|
/**
|
|
* For every new child of a node with the trx:transferred aspect run this.onCreateChildAssociation
|
|
*/
|
|
this.getPolicyComponent().bindAssociationBehaviour(
|
|
NodeServicePolicies.OnCreateChildAssociationPolicy.QNAME,
|
|
TransferModel.ASPECT_TRANSFERRED,
|
|
new JavaBehaviour(this, "onCreateChildAssociation", NotificationFrequency.EVERY_EVENT));
|
|
|
|
/**
|
|
* For every update of a transferred node
|
|
*/
|
|
this.getPolicyComponent().bindClassBehaviour(
|
|
ContentServicePolicies.OnContentUpdatePolicy.QNAME,
|
|
TransferModel.ASPECT_TRANSFERRED,
|
|
new JavaBehaviour(this, "onContentUpdate", NotificationFrequency.EVERY_EVENT));
|
|
|
|
/**
|
|
* For every copy of a transferred node run onCopyTransferred
|
|
*/
|
|
this.getPolicyComponent().bindClassBehaviour(
|
|
CopyServicePolicies.OnCopyNodePolicy.QNAME,
|
|
TransferModel.ASPECT_TRANSFERRED,
|
|
new JavaBehaviour(this, "onCopyTransferred", NotificationFrequency.EVERY_EVENT));
|
|
|
|
/**
|
|
* For every new child of a node with the trx:alien aspect run this.onCreateChildAssociation
|
|
*/
|
|
this.getPolicyComponent().bindAssociationBehaviour(
|
|
NodeServicePolicies.OnCreateChildAssociationPolicy.QNAME,
|
|
TransferModel.ASPECT_ALIEN,
|
|
new JavaBehaviour(this, "onCreateChildAssociation", NotificationFrequency.EVERY_EVENT));
|
|
|
|
/**
|
|
* For every node with the trx:alien aspect run this.beforeDeleteNode
|
|
*/
|
|
this.getPolicyComponent().bindClassBehaviour(
|
|
NodeServicePolicies.BeforeDeleteNodePolicy.QNAME,
|
|
TransferModel.ASPECT_ALIEN,
|
|
new JavaBehaviour(this, "beforeDeleteNode", NotificationFrequency.EVERY_EVENT));
|
|
|
|
/**
|
|
* For every restore of a node with the trx:alien aspect
|
|
*/
|
|
this.getPolicyComponent().bindClassBehaviour(
|
|
NodeServicePolicies.OnRestoreNodePolicy.QNAME,
|
|
TransferModel.ASPECT_ALIEN,
|
|
new JavaBehaviour(this, "onRestoreNode", NotificationFrequency.EVERY_EVENT));
|
|
|
|
/**
|
|
* For every move of a node with the trx:alien aspect.
|
|
*/
|
|
this.getPolicyComponent().bindClassBehaviour(
|
|
NodeServicePolicies.OnMoveNodePolicy.QNAME,
|
|
TransferModel.ASPECT_ALIEN,
|
|
new JavaBehaviour(this, "onMoveNode", NotificationFrequency.EVERY_EVENT));
|
|
|
|
/**
|
|
* For every copy of an alien node remove the alien aspect
|
|
*/
|
|
this.getPolicyComponent().bindClassBehaviour(
|
|
CopyServicePolicies.OnCopyNodePolicy.QNAME,
|
|
TransferModel.ASPECT_ALIEN,
|
|
new JavaBehaviour(this, "onCopyAlien", NotificationFrequency.EVERY_EVENT));
|
|
}
|
|
|
|
/*
|
|
* (non-Javadoc)
|
|
*
|
|
* @see
|
|
* org.alfresco.repo.web.scripts.transfer.TransferReceiver#getStagingFolder(org.alfresco.service.cmr.repository.
|
|
* NodeRef)
|
|
*/
|
|
public File getStagingFolder(String transferId)
|
|
{
|
|
if (transferId == null)
|
|
{
|
|
throw new IllegalArgumentException("transferId = " + transferId);
|
|
}
|
|
NodeRef transferNodeRef = new NodeRef(transferId);
|
|
File tempFolder;
|
|
String tempFolderPath = rootStagingDirectory + "/" + transferNodeRef.getId();
|
|
tempFolder = new File(tempFolderPath);
|
|
if (!tempFolder.exists())
|
|
{
|
|
if (!tempFolder.mkdirs())
|
|
{
|
|
tempFolder = null;
|
|
throw new TransferException(MSG_FAILED_TO_CREATE_STAGING_FOLDER, new Object[] { transferId });
|
|
}
|
|
}
|
|
return tempFolder;
|
|
|
|
}
|
|
|
|
public NodeRef getTempFolder(String transferId)
|
|
{
|
|
NodeRef transferTempFolder = singletonCache.get(KEY_TRANSFER_TEMP_NODEREF);
|
|
|
|
// Have we already resolved the node that is the temp folder?
|
|
// If not then do so.
|
|
if (transferTempFolder == null)
|
|
{
|
|
ResultSet rs = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, SearchService.LANGUAGE_XPATH,
|
|
transferTempFolderPath);
|
|
if (rs.length() > 0)
|
|
{
|
|
transferTempFolder = rs.getNodeRef(0);
|
|
singletonCache.put(KEY_TRANSFER_TEMP_NODEREF, transferTempFolder);
|
|
}
|
|
else
|
|
{
|
|
throw new TransferException(MSG_TRANSFER_TEMP_FOLDER_NOT_FOUND, new Object[] { transferId,
|
|
transferTempFolderPath });
|
|
}
|
|
}
|
|
|
|
NodeRef transferNodeRef = new NodeRef(transferId);
|
|
String tempTransferFolderName = transferNodeRef.getId();
|
|
NodeRef tempFolderNode = null;
|
|
QName folderName = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, tempTransferFolderName);
|
|
|
|
// Do we already have a temp folder for this transfer?
|
|
List<ChildAssociationRef> tempChildren = nodeService.getChildAssocs(transferTempFolder,
|
|
RegexQNamePattern.MATCH_ALL, folderName);
|
|
if (tempChildren.isEmpty())
|
|
{
|
|
// No, we don't have a temp folder for this transfer yet. Create it...
|
|
Map<QName, Serializable> props = new HashMap<QName, Serializable>();
|
|
props.put(ContentModel.PROP_NAME, tempTransferFolderName);
|
|
tempFolderNode = nodeService.createNode(transferTempFolder, ContentModel.ASSOC_CONTAINS, folderName,
|
|
TransferModel.TYPE_TEMP_TRANSFER_STORE, props).getChildRef();
|
|
}
|
|
else
|
|
{
|
|
// Yes, we do have a temp folder for this transfer already. Return it.
|
|
tempFolderNode = tempChildren.get(0).getChildRef();
|
|
}
|
|
return tempFolderNode;
|
|
|
|
}
|
|
|
|
/*
|
|
* (non-Javadoc)
|
|
*
|
|
* @see org.alfresco.repo.web.scripts.transfer.TransferReceiver#start()
|
|
*/
|
|
public String start(String fromRepositoryId, boolean transferToSelf, TransferVersion fromVersion)
|
|
{
|
|
log.debug("Start transfer");
|
|
|
|
/**
|
|
* Check that transfer is allowed to this repository
|
|
*/
|
|
checkTransfer(fromRepositoryId, transferToSelf);
|
|
|
|
/**
|
|
* Check that the versions are compatible
|
|
*/
|
|
TransferVersion toVersion = getVersion();
|
|
|
|
if(!getTransferVersionChecker().checkTransferVersions(fromVersion, toVersion))
|
|
{
|
|
throw new TransferException(MSG_INCOMPATIBLE_VERSIONS, new Object[] {"None", fromVersion, toVersion});
|
|
}
|
|
|
|
/**
|
|
* First get the transfer lock for this domain
|
|
*/
|
|
String tenantDomain = tenantService.getUserDomain(AuthenticationUtil.getRunAsUser());
|
|
String lockStr = tenantDomain.isEmpty() ? "transfer.server.default" : "transfer.server.tenant." + tenantDomain;
|
|
QName lockQName = QName.createQName(TransferModel.TRANSFER_MODEL_1_0_URI, lockStr);
|
|
Lock lock = new Lock(lockQName);
|
|
|
|
try
|
|
{
|
|
TransferServicePolicies.BeforeStartInboundTransferPolicy beforeStartPolicy =
|
|
beforeStartInboundTransferDelegate.get(TransferModel.TYPE_TRANSFER_RECORD);
|
|
beforeStartPolicy.beforeStartInboundTransfer();
|
|
|
|
lock.makeLock();
|
|
|
|
/**
|
|
* Transfer Lock held if we get this far
|
|
*/
|
|
String transferId = null;
|
|
|
|
try
|
|
{
|
|
/**
|
|
* Now create a transfer record and use its NodeRef as the transfer id
|
|
*/
|
|
RetryingTransactionHelper txHelper = transactionService.getRetryingTransactionHelper();
|
|
|
|
transferId = txHelper.doInTransaction(
|
|
new RetryingTransactionHelper.RetryingTransactionCallback<String>()
|
|
{
|
|
public String execute() throws Throwable
|
|
{
|
|
final NodeRef relatedTransferRecord = createTransferRecord();
|
|
String transferId = relatedTransferRecord.toString();
|
|
getTempFolder(transferId);
|
|
getStagingFolder(transferId);
|
|
|
|
TransferServicePolicies.OnStartInboundTransferPolicy onStartPolicy =
|
|
onStartInboundTransferDelegate.get(TransferModel.TYPE_TRANSFER_RECORD);
|
|
onStartPolicy.onStartInboundTransfer(transferId);
|
|
|
|
return transferId;
|
|
}
|
|
}, false, true);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
log.debug("Exception while staring transfer", e);
|
|
log.debug("releasing lock - we never created the transfer id");
|
|
lock.releaseLock();
|
|
throw new TransferException(MSG_ERROR_WHILE_STARTING, e);
|
|
}
|
|
|
|
/**
|
|
* Here if we have begun a transfer and have a valid transfer id
|
|
*/
|
|
lock.transferId = transferId;
|
|
locks.put(transferId, lock);
|
|
log.info("transfer started:" + transferId);
|
|
lock.enableLockTimeout();
|
|
return transferId;
|
|
|
|
}
|
|
catch (LockAcquisitionException lae)
|
|
{
|
|
log.debug("transfer lock is already taken", lae);
|
|
// lock is already taken.
|
|
throw new TransferException(MSG_TRANSFER_LOCK_UNAVAILABLE);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return
|
|
*/
|
|
private NodeRef createTransferRecord()
|
|
{
|
|
log.debug("Receiver createTransferRecord");
|
|
NodeRef inboundTransferRecordsFolder = singletonCache.get(KEY_INBOUND_TRANSFER_RECORDS_NODEREF);
|
|
|
|
if (inboundTransferRecordsFolder == null)
|
|
{
|
|
log.debug("Trying to find transfer records folder: " + inboundTransferRecordsPath);
|
|
ResultSet rs = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, SearchService.LANGUAGE_XPATH,
|
|
inboundTransferRecordsPath);
|
|
if (rs.length() > 0)
|
|
{
|
|
inboundTransferRecordsFolder = rs.getNodeRef(0);
|
|
singletonCache.put(KEY_INBOUND_TRANSFER_RECORDS_NODEREF, inboundTransferRecordsFolder);
|
|
log.debug("Found inbound transfer records folder: " + inboundTransferRecordsFolder);
|
|
}
|
|
else
|
|
{
|
|
throw new TransferException(MSG_INBOUND_TRANSFER_FOLDER_NOT_FOUND,
|
|
new Object[] { inboundTransferRecordsPath });
|
|
}
|
|
}
|
|
|
|
SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmssSSSZ");
|
|
String timeNow = format.format(new Date());
|
|
String name = timeNow + ".xml";
|
|
|
|
QName recordName = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, name);
|
|
|
|
Map<QName, Serializable> props = new HashMap<QName, Serializable>();
|
|
props.put(ContentModel.PROP_NAME, name);
|
|
props.put(TransferModel.PROP_PROGRESS_POSITION, 0);
|
|
props.put(TransferModel.PROP_PROGRESS_ENDPOINT, 1);
|
|
props.put(TransferModel.PROP_TRANSFER_STATUS, TransferProgress.Status.PRE_COMMIT.toString());
|
|
|
|
log.debug("Creating transfer record with name: " + name);
|
|
ChildAssociationRef assoc = nodeService.createNode(inboundTransferRecordsFolder, ContentModel.ASSOC_CONTAINS,
|
|
recordName, TransferModel.TYPE_TRANSFER_RECORD, props);
|
|
log.debug("<-createTransferRecord: " + assoc.getChildRef());
|
|
|
|
return assoc.getChildRef();
|
|
}
|
|
|
|
/**
|
|
* Timeout a transfer. Called after the lock has been released via a timeout.
|
|
*
|
|
* This is the last chance to clean up.
|
|
*
|
|
* @param transferId
|
|
*/
|
|
private void timeout(final String transferId)
|
|
{
|
|
log.info("Inbound Transfer has timed out transferId:" + transferId);
|
|
/*
|
|
* There is no transaction or authentication context in this method since it is called via a
|
|
* timer thread.
|
|
*/
|
|
final RetryingTransactionCallback<Void> timeoutCB = new RetryingTransactionCallback<Void>() {
|
|
|
|
|
|
public Void execute() throws Throwable
|
|
{
|
|
TransferProgress progress = getProgressMonitor().getProgress(transferId);
|
|
|
|
if (progress.getStatus().equals(TransferProgress.Status.PRE_COMMIT))
|
|
{
|
|
log.warn("Inbound Transfer Lock Timeout - transferId:" + transferId);
|
|
/**
|
|
* Did not get out of PRE_COMMIT. The client has probably "gone away" after calling
|
|
* "start", but before calling commit, cancel or error.
|
|
*/
|
|
locks.remove(transferId);
|
|
removeTempFolders(transferId);
|
|
Object[] msgParams = { transferId };
|
|
getProgressMonitor().logException(transferId, "transfer timeout", new TransferException(MSG_LOCK_TIMED_OUT, msgParams));
|
|
getProgressMonitor().updateStatus(transferId, TransferProgress.Status.ERROR);
|
|
}
|
|
else
|
|
{
|
|
// We got beyond PRE_COMMIT, therefore leave the clean up to either
|
|
// commit, cancel or error command, since there may still be "in-flight"
|
|
// transfer in another thread. Although why, in that case, are we here?
|
|
log.warn("Inbound Transfer Lock Timeout - already past PRE-COMMIT - do no cleanup transferId:" + transferId);
|
|
}
|
|
return null;
|
|
}
|
|
};
|
|
|
|
AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork<String>()
|
|
{
|
|
public String doWork() throws Exception
|
|
{
|
|
transactionService.getRetryingTransactionHelper().doInTransaction(timeoutCB, false, true);
|
|
return null;
|
|
}
|
|
}, AuthenticationUtil.getSystemUserName());
|
|
}
|
|
|
|
/*
|
|
* (non-Javadoc)
|
|
*
|
|
* @see org.alfresco.repo.web.scripts.transfer.TransferReceiver#end(org.alfresco.service.cmr.repository.NodeRef)
|
|
*/
|
|
public void end(final String transferId)
|
|
{
|
|
if (log.isDebugEnabled())
|
|
{
|
|
log.debug("Request to end transfer " + transferId);
|
|
}
|
|
if (transferId == null)
|
|
{
|
|
throw new IllegalArgumentException("transferId = null");
|
|
}
|
|
|
|
try
|
|
{
|
|
Lock lock = locks.get(transferId);
|
|
if(lock != null)
|
|
{
|
|
log.debug("releasing lock:" + lock.lockToken);
|
|
lock.releaseLock();
|
|
locks.remove(transferId);
|
|
}
|
|
|
|
removeTempFolders(transferId);
|
|
|
|
|
|
//Fire the OnEndInboundTransfer policy
|
|
Set<NodeRef> createdNodes = Collections.emptySet();
|
|
Set<NodeRef> updatedNodes = Collections.emptySet();
|
|
Set<NodeRef> deletedNodes = Collections.emptySet();
|
|
TransferChangesRecord changesRecord = progressMonitor.removeChangeRecord(transferId);
|
|
if (changesRecord != null)
|
|
{
|
|
createdNodes = new HashSet<NodeRef>(changesRecord.getCreatedNodes());
|
|
updatedNodes = new HashSet<NodeRef>(changesRecord.getUpdatedNodes());
|
|
deletedNodes = new HashSet<NodeRef>(changesRecord.getDeletedNodes());
|
|
}
|
|
TransferServicePolicies.OnEndInboundTransferPolicy onEndPolicy =
|
|
onEndInboundTransferDelegate.get(TransferModel.TYPE_TRANSFER_RECORD);
|
|
onEndPolicy.onEndInboundTransfer(transferId, createdNodes, updatedNodes, deletedNodes);
|
|
}
|
|
catch (TransferException ex)
|
|
{
|
|
throw ex;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
throw new TransferException(MSG_ERROR_WHILE_ENDING_TRANSFER, new Object[] {transferId}, ex);
|
|
}
|
|
}
|
|
|
|
private void removeTempFolders(final String transferId)
|
|
{
|
|
NodeRef tempStoreNode = null;
|
|
try
|
|
{
|
|
log.debug("Deleting temporary store node...");
|
|
tempStoreNode = getTempFolder(transferId);
|
|
nodeService.deleteNode(tempStoreNode);
|
|
log.debug("Deleted temporary store node.");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
log.warn("Failed to delete temp store node for transfer id " + transferId +
|
|
"\nTemp store noderef = " + tempStoreNode);
|
|
}
|
|
|
|
File stagingFolder = null;
|
|
try
|
|
{
|
|
log.debug("delete staging folder " + transferId);
|
|
// Delete the staging folder.
|
|
stagingFolder = getStagingFolder(transferId);
|
|
deleteFile(stagingFolder);
|
|
log.debug("Staging folder deleted");
|
|
}
|
|
catch(Exception ex)
|
|
{
|
|
log.warn("Failed to delete staging folder for transfer id " + transferId +
|
|
"\nStaging folder = " + stagingFolder.toString());
|
|
}
|
|
}
|
|
|
|
|
|
public void cancel(String transferId) throws TransferException
|
|
{
|
|
// no need to check the lock
|
|
TransferProgress progress = getProgressMonitor().getProgress(transferId);
|
|
getProgressMonitor().updateStatus(transferId, TransferProgress.Status.CANCELLED);
|
|
if (progress.getStatus().equals(TransferProgress.Status.PRE_COMMIT))
|
|
{
|
|
end(transferId);
|
|
}
|
|
}
|
|
|
|
public void prepare(String transferId) throws TransferException
|
|
{
|
|
// Check that this transfer still owns the lock
|
|
Lock lock = checkLock(transferId);
|
|
try
|
|
{
|
|
|
|
}
|
|
finally
|
|
{
|
|
lock.enableLockTimeout();
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* @param stagingFolder
|
|
*/
|
|
private void deleteFile(File file)
|
|
{
|
|
if (file.isDirectory())
|
|
{
|
|
File[] fileList = file.listFiles();
|
|
if (fileList != null)
|
|
{
|
|
for (File currentFile : fileList)
|
|
{
|
|
deleteFile(currentFile);
|
|
}
|
|
}
|
|
}
|
|
file.delete();
|
|
}
|
|
|
|
public Lock checkLock(final String transferId) throws TransferException
|
|
{
|
|
if (transferId == null)
|
|
{
|
|
throw new IllegalArgumentException("nudgeLock: transferId = null");
|
|
}
|
|
|
|
Lock lock = locks.get(transferId);
|
|
if(lock != null)
|
|
{
|
|
if(lock.isActive())
|
|
{
|
|
lock.suspendLockTimeout();
|
|
return lock;
|
|
}
|
|
else
|
|
{
|
|
// lock is no longer active
|
|
log.debug("lock not active");
|
|
throw new TransferException(MSG_LOCK_TIMED_OUT, new Object[]{transferId});
|
|
|
|
}
|
|
}
|
|
else
|
|
{
|
|
log.debug("lock not found");
|
|
throw new TransferException(MSG_LOCK_NOT_FOUND, new Object[]{transferId});
|
|
// lock not found
|
|
}
|
|
}
|
|
|
|
public void saveSnapshot(String transferId, InputStream openStream) throws TransferException
|
|
{
|
|
// Check that this transfer still owns the lock
|
|
Lock lock = checkLock(transferId);
|
|
try
|
|
{
|
|
if (log.isDebugEnabled())
|
|
{
|
|
log.debug("Saving snapshot for transferId =" + transferId);
|
|
}
|
|
|
|
File snapshotFile = new File(getStagingFolder(transferId), SNAPSHOT_FILE_NAME);
|
|
try
|
|
{
|
|
if (snapshotFile.createNewFile())
|
|
{
|
|
FileCopyUtils.copy(openStream, new BufferedOutputStream(new FileOutputStream(snapshotFile)));
|
|
}
|
|
if (log.isDebugEnabled())
|
|
{
|
|
log.debug("Saved snapshot for transferId =" + transferId);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
throw new TransferException(MSG_ERROR_WHILE_STAGING_SNAPSHOT, new Object[]{transferId}, ex);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
lock.enableLockTimeout();
|
|
}
|
|
}
|
|
|
|
public void saveContent(String transferId, String contentFileId, InputStream contentStream)
|
|
throws TransferException
|
|
{
|
|
Lock lock = checkLock(transferId);
|
|
try
|
|
{
|
|
File stagedFile = new File(getStagingFolder(transferId), contentFileId);
|
|
if (stagedFile.createNewFile())
|
|
{
|
|
FileCopyUtils.copy(contentStream, new BufferedOutputStream(new FileOutputStream(stagedFile)));
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
throw new TransferException(MSG_ERROR_WHILE_STAGING_CONTENT, new Object[]{transferId, contentFileId}, ex);
|
|
}
|
|
finally
|
|
{
|
|
lock.enableLockTimeout();
|
|
}
|
|
}
|
|
|
|
public void commitAsync(String transferId)
|
|
{
|
|
/**
|
|
* A side-effect of checking the lock here is that the lock timeout is suspended.
|
|
*
|
|
*/
|
|
Lock lock = checkLock(transferId);
|
|
try
|
|
{
|
|
progressMonitor.updateStatus(transferId, Status.COMMIT_REQUESTED);
|
|
Action commitAction = actionService.createAction(TransferCommitActionExecuter.NAME);
|
|
commitAction.setParameterValue(TransferCommitActionExecuter.PARAM_TRANSFER_ID, transferId);
|
|
commitAction.setExecuteAsynchronously(true);
|
|
actionService.executeAction(commitAction, new NodeRef(transferId));
|
|
if (log.isDebugEnabled())
|
|
{
|
|
log.debug("Registered transfer commit for asynchronous execution: " + transferId);
|
|
}
|
|
}
|
|
catch (Exception error)
|
|
{
|
|
/**
|
|
* Error somewhere in the action service?
|
|
*/
|
|
//TODO consider whether the methods in this class should be retried/retryable..
|
|
|
|
// need to re-enable the lock timeout otherwise we will hold the lock forever...
|
|
lock.enableLockTimeout();
|
|
|
|
throw new TransferException(MSG_ERROR_WHILE_COMMITTING_TRANSFER, new Object[]{transferId}, error);
|
|
}
|
|
|
|
/**
|
|
* Lock intentionally not re-enabled here
|
|
*/
|
|
}
|
|
|
|
public void commit(final String transferId) throws TransferException
|
|
{
|
|
if (log.isDebugEnabled())
|
|
{
|
|
log.debug("Committing transferId=" + transferId);
|
|
}
|
|
|
|
/**
|
|
* A side-effect of checking the lock here is that it ensures that the lock timeout is suspended.
|
|
*/
|
|
checkLock(transferId);
|
|
|
|
/**
|
|
* Turn off rules while transfer is being committed.
|
|
*/
|
|
boolean rulesEnabled = ruleService.isEnabled();
|
|
ruleService.disableRules();
|
|
|
|
try
|
|
{
|
|
/* lock is going to be released */ checkLock(transferId);
|
|
progressMonitor.updateStatus(transferId, Status.COMMITTING);
|
|
|
|
RetryingTransactionHelper.RetryingTransactionCallback<Object> commitWork = new RetryingTransactionCallback<Object>()
|
|
{
|
|
public Object execute() throws Throwable
|
|
{
|
|
AlfrescoTransactionSupport.bindListener(new TransferCommitTransactionListener(transferId,
|
|
RepoTransferReceiverImpl.this));
|
|
|
|
List<TransferManifestProcessor> commitProcessors = manifestProcessorFactory.getCommitProcessors(
|
|
RepoTransferReceiverImpl.this, transferId);
|
|
|
|
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
|
|
SAXParser parser = saxParserFactory.newSAXParser();
|
|
File snapshotFile = getSnapshotFile(transferId);
|
|
|
|
if (snapshotFile.exists())
|
|
{
|
|
if (log.isDebugEnabled())
|
|
{
|
|
log.debug("Processing manifest file:" + snapshotFile.getAbsolutePath());
|
|
}
|
|
// We parse the file as many times as we have processors
|
|
for (TransferManifestProcessor processor : commitProcessors)
|
|
{
|
|
XMLTransferManifestReader reader = new XMLTransferManifestReader(processor);
|
|
|
|
//behaviourFilter.disableBehaviour(ContentModel.ASPECT_AUDITABLE);
|
|
behaviourFilter.disableBehaviour();
|
|
|
|
try
|
|
{
|
|
parser.parse(snapshotFile, reader);
|
|
}
|
|
finally
|
|
{
|
|
behaviourFilter.enableBehaviour();
|
|
}
|
|
parser.reset();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
progressMonitor.logException(transferId, "Unable to start commit. No snapshot file received",
|
|
new TransferException(MSG_NO_SNAPSHOT_RECEIVED, new Object[]{transferId}));
|
|
}
|
|
return null;
|
|
}
|
|
};
|
|
|
|
transactionService.getRetryingTransactionHelper().doInTransaction(commitWork, false, true);
|
|
|
|
Throwable error = progressMonitor.getProgress(transferId).getError();
|
|
if (error != null)
|
|
{
|
|
if (TransferException.class.isAssignableFrom(error.getClass()))
|
|
{
|
|
throw (TransferException) error;
|
|
}
|
|
else
|
|
{
|
|
throw new TransferException(MSG_ERROR_WHILE_COMMITTING_TRANSFER, new Object[]{transferId}, error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Successfully committed
|
|
*/
|
|
if (log.isDebugEnabled())
|
|
{
|
|
log.debug("Commit success transferId=" + transferId);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
if (TransferException.class.isAssignableFrom(ex.getClass()))
|
|
{
|
|
throw (TransferException) ex;
|
|
}
|
|
else
|
|
{
|
|
throw new TransferException(MSG_ERROR_WHILE_COMMITTING_TRANSFER, ex);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
if(rulesEnabled)
|
|
{
|
|
/**
|
|
* Turn rules back on if we turned them off earlier.
|
|
*/
|
|
ruleService.enableRules();
|
|
}
|
|
|
|
/**
|
|
* Clean up at the end of the transfer
|
|
*/
|
|
try
|
|
{
|
|
end(transferId);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
log.error("Failed to clean up transfer. Lock may still be in place: " + transferId, ex);
|
|
}
|
|
}
|
|
}
|
|
|
|
public TransferProgress getStatus(String transferId) throws TransferException
|
|
{
|
|
return getProgressMonitor().getProgress(transferId);
|
|
}
|
|
|
|
private File getSnapshotFile(String transferId)
|
|
{
|
|
return new File(getStagingFolder(transferId), SNAPSHOT_FILE_NAME);
|
|
}
|
|
|
|
/**
|
|
* @param searchService
|
|
* the searchService to set
|
|
*/
|
|
public void setSearchService(SearchService searchService)
|
|
{
|
|
this.searchService = searchService;
|
|
}
|
|
|
|
/**
|
|
* @param transactionService
|
|
* the transactionService to set
|
|
*/
|
|
public void setTransactionService(TransactionService transactionService)
|
|
{
|
|
this.transactionService = transactionService;
|
|
}
|
|
|
|
public void setTenantService(TenantService tenantService)
|
|
{
|
|
this.tenantService = tenantService;
|
|
}
|
|
|
|
public void setSingletonCache(SimpleCache<String, NodeRef> singletonCache)
|
|
{
|
|
this.singletonCache = singletonCache;
|
|
}
|
|
|
|
/**
|
|
* @param transferLockFolderPath
|
|
* the transferLockFolderPath to set
|
|
*/
|
|
public void setTransferLockFolderPath(String transferLockFolderPath)
|
|
{
|
|
this.transferLockFolderPath = transferLockFolderPath;
|
|
}
|
|
|
|
/**
|
|
* @param transferTempFolderPath
|
|
* the transferTempFolderPath to set
|
|
*/
|
|
public void setTransferTempFolderPath(String transferTempFolderPath)
|
|
{
|
|
this.transferTempFolderPath = transferTempFolderPath;
|
|
}
|
|
|
|
/**
|
|
* @param rootStagingDirectory
|
|
* the rootTransferFolder to set
|
|
*/
|
|
public void setRootStagingDirectory(String rootStagingDirectory)
|
|
{
|
|
this.rootStagingDirectory = rootStagingDirectory;
|
|
}
|
|
|
|
/**
|
|
* @param inboundTransferRecordsPath
|
|
* the inboundTransferRecordsPath to set
|
|
*/
|
|
public void setInboundTransferRecordsPath(String inboundTransferRecordsPath)
|
|
{
|
|
this.inboundTransferRecordsPath = inboundTransferRecordsPath;
|
|
}
|
|
|
|
/**
|
|
* @param nodeService
|
|
* the nodeService to set
|
|
*/
|
|
public void setNodeService(NodeService nodeService)
|
|
{
|
|
this.nodeService = nodeService;
|
|
}
|
|
|
|
/**
|
|
* @param manifestProcessorFactory
|
|
* the manifestProcessorFactory to set
|
|
*/
|
|
public void setManifestProcessorFactory(ManifestProcessorFactory manifestProcessorFactory)
|
|
{
|
|
this.manifestProcessorFactory = manifestProcessorFactory;
|
|
}
|
|
|
|
/**
|
|
* @param behaviourFilter
|
|
* the behaviourFilter to set
|
|
*/
|
|
public void setBehaviourFilter(BehaviourFilter behaviourFilter)
|
|
{
|
|
this.behaviourFilter = behaviourFilter;
|
|
}
|
|
|
|
/**
|
|
* @return the progressMonitor
|
|
*/
|
|
public TransferProgressMonitor getProgressMonitor()
|
|
{
|
|
return progressMonitor;
|
|
}
|
|
|
|
/**
|
|
* @param progressMonitor
|
|
* the progressMonitor to set
|
|
*/
|
|
public void setProgressMonitor(TransferProgressMonitor progressMonitor)
|
|
{
|
|
this.progressMonitor = new ChangeCapturingProgressMonitor(progressMonitor);
|
|
}
|
|
|
|
public void setActionService(ActionService actionService)
|
|
{
|
|
this.actionService = actionService;
|
|
}
|
|
|
|
/**
|
|
* Set the ruleService
|
|
* @param ruleService
|
|
* the ruleService to set
|
|
*/
|
|
public void setRuleService(RuleService ruleService)
|
|
{
|
|
this.ruleService = ruleService;
|
|
}
|
|
|
|
/**
|
|
* Get the rule service
|
|
* @return the rule service
|
|
*/
|
|
public RuleService getRuleService()
|
|
{
|
|
return this.ruleService;
|
|
}
|
|
|
|
/**
|
|
* Generate the requsite
|
|
*/
|
|
public void generateRequsite(String transferId, OutputStream out) throws TransferException
|
|
{
|
|
log.debug("Generate Requsite for transfer:" + transferId);
|
|
try
|
|
{
|
|
File snapshotFile = getSnapshotFile(transferId);
|
|
|
|
if (snapshotFile.exists())
|
|
{
|
|
log.debug("snapshot does exist");
|
|
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
|
|
SAXParser parser = saxParserFactory.newSAXParser();
|
|
OutputStreamWriter dest = new OutputStreamWriter(out, "UTF-8");
|
|
|
|
XMLTransferRequsiteWriter writer = new XMLTransferRequsiteWriter(dest);
|
|
TransferManifestProcessor processor = manifestProcessorFactory.getRequsiteProcessor(
|
|
RepoTransferReceiverImpl.this,
|
|
transferId,
|
|
writer);
|
|
|
|
XMLTransferManifestReader reader = new XMLTransferManifestReader(processor);
|
|
|
|
/**
|
|
* Now run the parser
|
|
*/
|
|
parser.parse(snapshotFile, reader);
|
|
|
|
/**
|
|
* And flush the destination in case any content remains in the writer.
|
|
*/
|
|
dest.flush();
|
|
|
|
}
|
|
log.debug("Generate Requsite done transfer:" + transferId);
|
|
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
if (TransferException.class.isAssignableFrom(ex.getClass()))
|
|
{
|
|
throw (TransferException) ex;
|
|
}
|
|
else
|
|
{
|
|
throw new TransferException(MSG_ERROR_WHILE_GENERATING_REQUISITE, ex);
|
|
}
|
|
}
|
|
}
|
|
|
|
public InputStream getTransferReport(String transferId)
|
|
{
|
|
return progressMonitor.getLogInputStream(transferId);
|
|
}
|
|
|
|
public void setPolicyComponent(PolicyComponent policyComponent)
|
|
{
|
|
this.policyComponent = policyComponent;
|
|
}
|
|
|
|
public PolicyComponent getPolicyComponent()
|
|
{
|
|
return policyComponent;
|
|
}
|
|
|
|
/**
|
|
* When a new node is created as a child of a Transferred or Alien node then
|
|
* the new node needs to be marked as an alien.
|
|
* <p>
|
|
* Then the tree needs to be walked upwards to mark all parent
|
|
* transferred nodes as alien.
|
|
*/
|
|
public void onCreateChildAssociation(ChildAssociationRef childAssocRef,
|
|
boolean isNewNode)
|
|
{
|
|
|
|
log.debug("on create child association to transferred node");
|
|
|
|
final String localRepositoryId = descriptorService.getCurrentRepositoryDescriptor().getId();
|
|
alienProcessor.onCreateChild(childAssocRef, localRepositoryId, isNewNode);
|
|
}
|
|
|
|
/**
|
|
* When an alien node is deleted the it may be the last alien invader
|
|
* <p>
|
|
* Walk the tree checking the invasion status!
|
|
*/
|
|
public void beforeDeleteNode(NodeRef deletedNodeRef)
|
|
{
|
|
log.debug("on delete node - need to check for transferred node");
|
|
alienProcessor.beforeDeleteAlien(deletedNodeRef, null);
|
|
}
|
|
|
|
/**
|
|
* When a transferred node is restored it may be a new invader or it may no
|
|
* longer be an invader.
|
|
* <p>
|
|
* Walk the tree checking the invasion status!
|
|
*/
|
|
public void onRestoreNode(ChildAssociationRef childAssocRef)
|
|
{
|
|
log.debug("on restore node");
|
|
log.debug("restoredAssocRef:" + childAssocRef);
|
|
alienProcessor.afterMoveAlien(childAssocRef);
|
|
}
|
|
|
|
/**
|
|
* When an alien node is moved it may un-invade its old location and invade a new
|
|
* location. The node may also cease to be alien.
|
|
*/
|
|
public void onMoveNode(ChildAssociationRef oldChildAssocRef,
|
|
ChildAssociationRef newChildAssocRef)
|
|
{
|
|
|
|
log.debug("onMoveNode");
|
|
log.debug("oldChildAssocRef:" + oldChildAssocRef);
|
|
log.debug("newChildAssocRef:" + newChildAssocRef);
|
|
|
|
NodeRef oldParentRef = oldChildAssocRef.getParentRef();
|
|
NodeRef newParentRef = newChildAssocRef.getParentRef();
|
|
|
|
if(newParentRef.equals(oldParentRef))
|
|
{
|
|
log.debug("old parent and new parent are the same - this is a rename, do nothing");
|
|
}
|
|
else
|
|
{
|
|
if(log.isDebugEnabled())
|
|
{
|
|
log.debug("moving node from oldParentRef:" + oldParentRef +" to:" + newParentRef);
|
|
}
|
|
alienProcessor.beforeDeleteAlien(newChildAssocRef.getChildRef(), oldChildAssocRef);
|
|
alienProcessor.afterMoveAlien(newChildAssocRef);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* When a transferred node is copied, don't copy the transferred aspect.
|
|
*/
|
|
public CopyBehaviourCallback onCopyTransferred(QName classRef,
|
|
CopyDetails copyDetails)
|
|
{
|
|
return TransferredAspectCopyBehaviourCallback.INSTANCE;
|
|
}
|
|
|
|
/**
|
|
* When an alien node is copied, don't copy the alien aspect.
|
|
*/
|
|
public CopyBehaviourCallback onCopyAlien(QName classRef,
|
|
CopyDetails copyDetails)
|
|
{
|
|
return AlienAspectCopyBehaviourCallback.INSTANCE;
|
|
}
|
|
|
|
/**
|
|
* Extends the default copy behaviour to prevent copying of transferred aspect and properties.
|
|
*
|
|
* @author Mark Rogers
|
|
* @since 3.4
|
|
*/
|
|
private static class TransferredAspectCopyBehaviourCallback extends DefaultCopyBehaviourCallback
|
|
{
|
|
private static final CopyBehaviourCallback INSTANCE = new TransferredAspectCopyBehaviourCallback();
|
|
|
|
/**
|
|
* @return Returns an empty map
|
|
*/
|
|
@Override
|
|
public Map<QName, Serializable> getCopyProperties(
|
|
QName classQName, CopyDetails copyDetails, Map<QName, Serializable> properties)
|
|
{
|
|
return Collections.emptyMap();
|
|
}
|
|
|
|
/**
|
|
* Don't copy the transferred aspect.
|
|
*
|
|
* @return Returns <tt>true</tt> always
|
|
*/
|
|
@Override
|
|
public boolean getMustCopy(QName classQName, CopyDetails copyDetails)
|
|
{
|
|
if(classQName.equals(TransferModel.ASPECT_TRANSFERRED))
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Extends the default copy behaviour to prevent copying of alien aspect and properties.
|
|
*
|
|
* @author Mark Rogers
|
|
* @since 3.4
|
|
*/
|
|
private static class AlienAspectCopyBehaviourCallback extends DefaultCopyBehaviourCallback
|
|
{
|
|
private static final CopyBehaviourCallback INSTANCE = new AlienAspectCopyBehaviourCallback();
|
|
|
|
/**
|
|
* @return Returns an empty map
|
|
*/
|
|
@Override
|
|
public Map<QName, Serializable> getCopyProperties(
|
|
QName classQName, CopyDetails copyDetails, Map<QName, Serializable> properties)
|
|
{
|
|
return Collections.emptyMap();
|
|
}
|
|
|
|
/**
|
|
* Don't copy the transferred aspect.
|
|
*
|
|
* @return Returns <tt>true</tt> always
|
|
*/
|
|
@Override
|
|
public boolean getMustCopy(QName classQName, CopyDetails copyDetails)
|
|
{
|
|
if(classQName.equals(TransferModel.ASPECT_ALIEN))
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
public void setDescriptorService(DescriptorService descriptorService)
|
|
{
|
|
this.descriptorService = descriptorService;
|
|
}
|
|
|
|
public DescriptorService getDescriptorService()
|
|
{
|
|
return descriptorService;
|
|
}
|
|
|
|
public void setAlienProcessor(AlienProcessor alienProcessor)
|
|
{
|
|
this.alienProcessor = alienProcessor;
|
|
}
|
|
|
|
public AlienProcessor getAlienProcessor()
|
|
{
|
|
return alienProcessor;
|
|
}
|
|
|
|
@Override
|
|
public void onContentUpdate(NodeRef nodeRef, boolean newContent)
|
|
{
|
|
/**
|
|
* On update of a transferred node remove the from content from property.
|
|
*/
|
|
log.debug("on content update called:" + nodeRef);
|
|
if(newContent)
|
|
{
|
|
log.debug("new content remove PROP_FROM_CONTENT from node:" + nodeRef);
|
|
nodeService.setProperty(nodeRef, TransferModel.PROP_FROM_CONTENT, null);
|
|
}
|
|
}
|
|
|
|
public void setJobLockService(JobLockService jobLockService)
|
|
{
|
|
this.jobLockService = jobLockService;
|
|
}
|
|
|
|
public JobLockService getJobLockService()
|
|
{
|
|
return jobLockService;
|
|
}
|
|
|
|
public void setLockRetryCount(int lockRetryCount)
|
|
{
|
|
this.lockRetryCount = lockRetryCount;
|
|
}
|
|
|
|
public int getLockRetryCount()
|
|
{
|
|
return lockRetryCount;
|
|
}
|
|
|
|
public void setLockRetryWait(long lockRetryWait)
|
|
{
|
|
this.lockRetryWait = lockRetryWait;
|
|
}
|
|
|
|
public long getLockRetryWait()
|
|
{
|
|
return lockRetryWait;
|
|
}
|
|
|
|
public void setLockTimeOut(long lockTimeOut)
|
|
{
|
|
this.lockTimeOut = lockTimeOut;
|
|
}
|
|
|
|
public long getLockTimeOut()
|
|
{
|
|
return lockTimeOut;
|
|
}
|
|
|
|
public void setLockRefreshTime(long lockRefreshTime)
|
|
{
|
|
this.lockRefreshTime = lockRefreshTime;
|
|
}
|
|
|
|
public long getLockRefreshTime()
|
|
{
|
|
return lockRefreshTime;
|
|
}
|
|
|
|
/**
|
|
* A Transfer Lock
|
|
*/
|
|
private class Lock implements JobLockService.JobLockRefreshCallback
|
|
{
|
|
/**
|
|
* The name of the lock - unique for each domain
|
|
*/
|
|
QName lockQName;
|
|
|
|
/**
|
|
* The unique token for this lock instance.
|
|
*/
|
|
String lockToken;
|
|
|
|
/**
|
|
* The transfer that this lock belongs to.
|
|
*/
|
|
String transferId;
|
|
|
|
/**
|
|
* Is the lock active ?
|
|
*/
|
|
private boolean active = false;
|
|
|
|
/**
|
|
* Is the server processing ?
|
|
*/
|
|
private boolean processing = false;
|
|
|
|
/**
|
|
* When did we last check whether the lock is active
|
|
*/
|
|
long lastActive = System.currentTimeMillis();
|
|
|
|
public Lock(QName lockQName)
|
|
{
|
|
this.lockQName = lockQName;
|
|
}
|
|
|
|
|
|
/**
|
|
* Make the lock - called on main thread
|
|
*
|
|
* @throws LockAquisitionException
|
|
*/
|
|
public synchronized void makeLock()
|
|
{
|
|
if(log.isDebugEnabled())
|
|
{
|
|
log.debug("makeLock" + lockQName);
|
|
}
|
|
|
|
lockToken = getJobLockService().getLock(lockQName, getLockRefreshTime(), getLockRetryWait(), getLockRetryCount());
|
|
|
|
// Got the lock, so mark as active
|
|
active = true;
|
|
|
|
if (log.isDebugEnabled())
|
|
{
|
|
log.debug("lock taken: name" + lockQName + " token:" +lockToken);
|
|
log.debug("register lock callback, target lock refresh time :" + getLockRefreshTime());
|
|
}
|
|
getJobLockService().refreshLock(lockToken, lockQName, getLockRefreshTime(), this);
|
|
if (log.isDebugEnabled())
|
|
{
|
|
log.debug("refreshLock callback registered");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check that the lock is still active
|
|
*
|
|
* Called on main transfer thread as transfer proceeds.
|
|
* @throws TransferException (Lock timeout)
|
|
*/
|
|
public synchronized void suspendLockTimeout()
|
|
{
|
|
log.debug("suspend lock called");
|
|
if (active)
|
|
{
|
|
processing = true;
|
|
}
|
|
else
|
|
{
|
|
// lock is no longer active
|
|
log.debug("lock not active, throw timed out exception");
|
|
throw new TransferException(MSG_LOCK_TIMED_OUT);
|
|
}
|
|
}
|
|
|
|
public synchronized void enableLockTimeout()
|
|
{
|
|
long now = System.currentTimeMillis();
|
|
// Update lastActive to 1S boundary
|
|
if(now > lastActive + 1000L)
|
|
{
|
|
lastActive = now;
|
|
log.debug("start waiting : lastActive:" + lastActive);
|
|
}
|
|
|
|
processing = false;
|
|
}
|
|
|
|
/**
|
|
* Release the lock
|
|
*
|
|
* Called on main thread
|
|
*/
|
|
public synchronized void releaseLock()
|
|
{
|
|
if(log.isDebugEnabled())
|
|
{
|
|
log.debug("transfer service about to releaseLock : " + lockQName);
|
|
}
|
|
|
|
if (active)
|
|
{
|
|
active = false;
|
|
getJobLockService().releaseLock(lockToken, lockQName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called by Job Lock Service to determine whether the lock is still active
|
|
*/
|
|
@Override
|
|
public synchronized boolean isActive()
|
|
{
|
|
long now = System.currentTimeMillis();
|
|
|
|
if(active)
|
|
{
|
|
if(!processing)
|
|
{
|
|
if(now > lastActive + getLockTimeOut())
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(log.isDebugEnabled())
|
|
{
|
|
log.debug("transfer service callback isActive: " + active);
|
|
}
|
|
|
|
return active;
|
|
}
|
|
|
|
/**
|
|
* Called by Job Lock Service on release of the lock after time-out
|
|
*/
|
|
@Override
|
|
public synchronized void lockReleased()
|
|
{
|
|
if(active)
|
|
{
|
|
active = false;
|
|
log.info("transfer service: lock has timed out, timeout :" + lockQName);
|
|
timeout(transferId);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check Whether transfer is allowed from the specified repository.
|
|
* Called prior to "begin".
|
|
*/
|
|
|
|
private void checkTransfer(String fromRepository, boolean transferToSelf)
|
|
{
|
|
if(log.isDebugEnabled())
|
|
{
|
|
log.debug("checkTransfer fromRepository:" + fromRepository + ", transferToSelf:" + transferToSelf );
|
|
}
|
|
final String localRepositoryId = descriptorService.getCurrentRepositoryDescriptor().getId();
|
|
|
|
if(!transferToSelf)
|
|
{
|
|
if(fromRepository != null)
|
|
{
|
|
if(fromRepository.equalsIgnoreCase(localRepositoryId))
|
|
{
|
|
throw new TransferException(MSG_TRANSFER_TO_SELF);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw new TransferException("from repository id is missing");
|
|
}
|
|
}
|
|
}
|
|
|
|
public void setTransferVersionChecker(TransferVersionChecker transferVersionChecker)
|
|
{
|
|
this.transferVersionChecker = transferVersionChecker;
|
|
}
|
|
|
|
public TransferVersionChecker getTransferVersionChecker()
|
|
{
|
|
return transferVersionChecker;
|
|
}
|
|
|
|
@Override
|
|
public TransferVersion getVersion()
|
|
{
|
|
Descriptor d = descriptorService.getServerDescriptor();
|
|
// needs to be serverDescriptor to pick up versionEdition
|
|
return new TransferVersionImpl(d);
|
|
}
|
|
|
|
public void setTransferRootNode(String rootFileSystem)
|
|
{
|
|
//just ignore, no relevant for transferring on file system
|
|
}
|
|
}
|